Modifies OperationsService so that activities can request the resulf of operations...
authorDavid A. Velasco <dvelasco@solidgear.es>
Mon, 31 Mar 2014 11:14:47 +0000 (13:14 +0200)
committerDavid A. Velasco <dvelasco@solidgear.es>
Mon, 31 Mar 2014 11:14:47 +0000 (13:14 +0200)
src/com/owncloud/android/authentication/AuthenticatorActivity.java
src/com/owncloud/android/services/OperationsService.java

index 074751e..129da6c 100644 (file)
@@ -45,6 +45,7 @@ import android.support.v4.app.FragmentTransaction;
 import android.text.Editable;\r
 import android.text.InputType;\r
 import android.text.TextWatcher;\r
+import android.util.Log;\r
 import android.view.KeyEvent;\r
 import android.view.MotionEvent;\r
 import android.view.View;\r
@@ -124,6 +125,9 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
     private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";\r
     private static final String KEY_REFRESH_BUTTON_ENABLED = "KEY_REFRESH_BUTTON_ENABLED";\r
     //private static final String KEY_IS_SHARED_SUPPORTED = "KEY_IS_SHARE_SUPPORTED";\r
+    private static final String KEY_SERVER_AUTH_METHOD = "KEY_SERVER_AUTH_METHOD";\r
+    private static final String KEY_DETECT_AUTH_OP_ID = "KEY_DETECT_AUTH_OP_ID";\r
+\r
 \r
     private static final String AUTH_ON = "on";\r
     private static final String AUTH_OFF = "off";\r
@@ -144,12 +148,12 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
     private String mAuthMessageText;\r
     private int mAuthMessageVisibility, mServerStatusText, mServerStatusIcon;\r
     private boolean mServerIsChecked, mServerIsValid, mIsSslConn;\r
+    private AuthenticationMethod mServerAuthMethod = AuthenticationMethod.UNKNOWN;\r
+    private int mDetectAuthOpId = -1;\r
+\r
     private int mAuthStatusText, mAuthStatusIcon;    \r
     private TextView mAuthStatusLayout;\r
 \r
-    private ServiceConnection mOperationsConnection = null;\r
-    private OperationsServiceBinder mOperationsBinder = null;\r
-    \r
     private final Handler mHandler = new Handler();\r
     private Thread mOperationThread;\r
     private GetRemoteStatusOperation mOcServerChkOperation;\r
@@ -192,7 +196,6 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
     \r
     private OperationsServiceBinder mOperationsServiceBinder = null;\r
 \r
-\r
     /**\r
      * {@inheritDoc}\r
      * \r
@@ -204,31 +207,17 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         getWindow().requestFeature(Window.FEATURE_NO_TITLE);\r
 \r
         // bind to Operations Service\r
-        mOperationsConnection = new ServiceConnection() {\r
-\r
-            @Override\r
-            public void onServiceConnected(ComponentName name, IBinder service) {\r
-                Log_OC.d(TAG, "Operations service connected");\r
-                mOperationsBinder = (OperationsServiceBinder) service;\r
-            }\r
-\r
-            @Override\r
-            public void onServiceDisconnected(ComponentName name) {\r
-                Log_OC.d(TAG, "Operations service crashed");\r
-                mOperationsBinder = null;\r
-            }\r
-            \r
-        };\r
+        mOperationsServiceConnection = new OperationsServiceConnection();\r
         if (!bindService(new Intent(this, OperationsService.class), \r
-                        mOperationsConnection, \r
-                        Context.BIND_AUTO_CREATE)) {\r
+                mOperationsServiceConnection, \r
+                Context.BIND_AUTO_CREATE)) {\r
             Toast.makeText(this, \r
                     R.string.error_cant_bind_to_operations_service, \r
                     Toast.LENGTH_LONG)\r
                         .show();\r
             finish();\r
         }\r
-\r
+                \r
         /// set view and get references to view elements\r
         setContentView(R.layout.account_setup);\r
         mAuthMessage = (TextView) findViewById(R.id.auth_message);\r
@@ -347,6 +336,10 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
             refreshButtonEnabled = savedInstanceState.getBoolean(KEY_REFRESH_BUTTON_ENABLED);\r
 \r
 \r
+            mServerAuthMethod = AuthenticationMethod.valueOf(\r
+                    savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));\r
+            mDetectAuthOpId = savedInstanceState.getInt(KEY_DETECT_AUTH_OP_ID);\r
+\r
         }\r
 \r
         if (mAuthMessageVisibility== View.VISIBLE) {\r
@@ -433,8 +426,6 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
             }\r
         });\r
         \r
-        mOperationsServiceConnection = new OperationsServiceConnection();\r
-        bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection, Context.BIND_AUTO_CREATE);\r
     }\r
 \r
 \r
@@ -487,6 +478,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
      */\r
     @Override\r
     protected void onSaveInstanceState(Bundle outState) {\r
+        //Log.wtf(TAG, "onSaveInstanceState init" );\r
         super.onSaveInstanceState(outState);\r
 \r
         /// connection state and info\r
@@ -517,7 +509,10 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
 \r
         // refresh button enabled\r
         outState.putBoolean(KEY_REFRESH_BUTTON_ENABLED, (mRefreshButton.getVisibility() == View.VISIBLE));\r
-\r
+        \r
+        outState.putString(KEY_SERVER_AUTH_METHOD, mServerAuthMethod.name());\r
+        outState.putInt(KEY_DETECT_AUTH_OP_ID, mDetectAuthOpId);\r
+        //Log.wtf(TAG, "onSaveInstanceState end" );\r
     }\r
 \r
 \r
@@ -538,33 +533,15 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
     }\r
 \r
 \r
-    @Override \r
-    protected void onStart() {\r
-        if (mOperationsServiceBinder != null) {\r
-            mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);\r
-        }\r
-        \r
-        super.onStart();\r
-    }\r
-    \r
-    \r
-    @Override \r
-    protected void onStop() {\r
-        if (mOperationsServiceBinder != null) {\r
-            mOperationsServiceBinder.removeOperationListener(this);\r
-        }\r
-        super.onStop();\r
-    }\r
-    \r
-\r
-\r
     /**\r
      * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and \r
      * deferred in {@link #onNewIntent(Intent)}, is processed here.\r
      */\r
     @Override\r
     protected void onResume() {\r
+        //Log.wtf(TAG, "onResume init" );\r
         super.onResume();\r
+        \r
         if (mAction == ACTION_UPDATE_TOKEN && mJustCreated && getIntent().getBooleanExtra(EXTRA_ENFORCED_UPDATE, false)) {\r
             if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) {\r
                 //Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show();\r
@@ -581,17 +558,33 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         if (mNewCapturedUriFromOAuth2Redirection != null) {\r
             getOAuth2AccessTokenFromCapturedRedirection();            \r
         }\r
-\r
+        \r
         mJustCreated = false;\r
 \r
+        if (mOperationsServiceBinder != null) {\r
+            doOnResumeAndBound();\r
+        }\r
+        \r
+        //Log.wtf(TAG, "onResume end" );\r
     }\r
+\r
     \r
+    @Override\r
+    protected void onPause() {\r
+        //Log.wtf(TAG, "onPause init" );\r
+        if (mOperationsServiceBinder != null) {\r
+            //Log.wtf(TAG, "unregistering to listen for operation callbacks" );\r
+            mOperationsServiceBinder.removeOperationListener(this);\r
+        }\r
+        super.onPause();\r
+        //Log.wtf(TAG, "onPause end" );\r
+    }\r
     \r
     @Override\r
     protected void onDestroy() {\r
-        if (mOperationsConnection != null) {\r
-            unbindService(mOperationsConnection);\r
-            mOperationsBinder = null;\r
+        if (mOperationsServiceConnection != null) {\r
+            unbindService(mOperationsServiceConnection);\r
+            mOperationsServiceBinder = null;\r
         }\r
         super.onDestroy();\r
     }\r
@@ -885,13 +878,15 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
             onGetUserNameFinish((GetRemoteUserNameOperation) operation, result);\r
 \r
         } else if (operation instanceof DetectAuthenticationMethodOperation) {\r
-            onDetectAutheticationFinish((DetectAuthenticationMethodOperation) operation, result);\r
+            Log.wtf(TAG, "received detection response through callback" );\r
+            onDetectAuthenticationFinish(result);\r
         }\r
 \r
     }\r
 \r
-    private void onDetectAutheticationFinish(DetectAuthenticationMethodOperation operation, RemoteOperationResult result) {\r
+    private void onDetectAuthenticationFinish(RemoteOperationResult result) {\r
         // Read authentication method\r
+        mDetectAuthOpId = -1;\r
         if (result.getData().size() > 0) {\r
             AuthenticationMethod authMethod = (AuthenticationMethod) result.getData().get(0);\r
             String basic = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType());\r
@@ -1037,11 +1032,19 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType);\r
         \r
         /// test credentials \r
-        Intent service = new Intent(this, OperationsService.class);        \r
-        service.setAction(OperationsService.ACTION_DETECT_AUTHENTICATION_METHOD);\r
-        service.putExtra(OperationsService.EXTRA_SERVER_URL, mHostBaseUrl);\r
-        service.putExtra(OperationsService.EXTRA_WEBDAV_PATH, webdav_path);\r
-        startService(service);\r
+        //Intent detectAuthIntent = new Intent(this, OperationsService.class);\r
+        Intent detectAuthIntent = new Intent();\r
+        detectAuthIntent.setAction(OperationsService.ACTION_DETECT_AUTHENTICATION_METHOD);\r
+        detectAuthIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mHostBaseUrl);\r
+        detectAuthIntent.putExtra(OperationsService.EXTRA_WEBDAV_PATH, webdav_path);\r
+        \r
+        //if (mOperationsBinder != null) {  // let's let it crash to detect if is really possible\r
+        mServerAuthMethod = AuthenticationMethod.UNKNOWN;\r
+        if (mOperationsServiceBinder != null) {\r
+            //Log.wtf(TAG, "starting detection..." );\r
+            mDetectAuthOpId = mOperationsServiceBinder.newOperation(detectAuthIntent);\r
+        }\r
+        //}\r
     }\r
 \r
 \r
@@ -1866,18 +1869,35 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         }\r
 \r
     }\r
-\r
+    \r
+    \r
+    private void doOnResumeAndBound() {\r
+        //Log.wtf(TAG, "registering to listen for operation callbacks" );\r
+        mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);\r
+        \r
+        if (mDetectAuthOpId != -1) {\r
+            RemoteOperationResult result = \r
+                    mOperationsServiceBinder.getOperationResultIfFinished(mDetectAuthOpId);\r
+            if (result != null) {\r
+                //Log.wtf(TAG, "found result of operation finished while rotating");\r
+                onDetectAuthenticationFinish(result);\r
+            }\r
+        }\r
+    }\r
+    \r
     /** \r
-     * Implements callback methods for service binding. Passed as a parameter to { \r
+     * Implements callback methods for service binding. \r
      */\r
     private class OperationsServiceConnection implements ServiceConnection {\r
 \r
         @Override\r
         public void onServiceConnected(ComponentName component, IBinder service) {\r
             if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) {\r
-                Log_OC.d(TAG, "Operations service connected");\r
+                //Log_OC.wtf(TAG, "Operations service connected");\r
                 mOperationsServiceBinder = (OperationsServiceBinder) service;\r
-                mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);\r
+                \r
+                doOnResumeAndBound();\r
+                \r
             } else {\r
                 return;\r
             }\r
@@ -1887,11 +1907,11 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         @Override\r
         public void onServiceDisconnected(ComponentName component) {\r
             if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) {\r
-                Log_OC.d(TAG, "Operations service disconnected");\r
+                Log_OC.e(TAG, "Operations service crashed");\r
                 mOperationsServiceBinder = null;\r
-                // TODO whatever could be waiting for the service is unbound\r
             }\r
         }\r
     \r
     }\r
+    \r
 }\r
index 0df160a..0ccd909 100644 (file)
 package com.owncloud.android.services;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.lib.common.OwnCloudClientFactory;
@@ -68,7 +68,11 @@ public class OperationsService extends Service {
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
     public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
 
-    private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+    private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = 
+            new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+    
+    private ConcurrentMap<Integer, RemoteOperationResult> mOperationResults =
+            new ConcurrentHashMap<Integer, RemoteOperationResult>();
     
     private static class Target {
         public Uri mServerUrl = null;
@@ -101,6 +105,7 @@ public class OperationsService extends Service {
         mBinder = new OperationsServiceBinder();
     }
 
+    
     /**
      * Entry point to add a new operation to the queue of operations.
      * 
@@ -112,51 +117,11 @@ public class OperationsService extends Service {
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (!intent.hasExtra(EXTRA_ACCOUNT) && !intent.hasExtra(EXTRA_SERVER_URL)) {
-            Log_OC.e(TAG, "Not enough information provided in intent");
-            return START_NOT_STICKY;
-        }
-        try {
-            Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
-            String serverUrl = intent.getStringExtra(EXTRA_SERVER_URL);
-            
-            Target target = new Target(account, (serverUrl == null) ? null : Uri.parse(serverUrl));
-            RemoteOperation operation = null;
-            
-            String action = intent.getAction();
-            if (action.equals(ACTION_CREATE_SHARE)) {  // Create Share
-                String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
-                Intent sendIntent = intent.getParcelableExtra(EXTRA_SEND_INTENT);
-                if (remotePath.length() > 0) {
-                    operation = new CreateShareOperation(remotePath, ShareType.PUBLIC_LINK, 
-                            "", false, "", 1, sendIntent);
-                }
-            } else if (action.equals(ACTION_UNSHARE)) {  // Unshare file
-                String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
-                if (remotePath.length() > 0) {
-                    operation = new UnshareLinkOperation(remotePath, this.getApplicationContext());
-                }
-            } else if (action.equals(ACTION_DETECT_AUTHENTICATION_METHOD)) { // Detect Authentication Method
-                String webdav_url = serverUrl + intent.getStringExtra(EXTRA_WEBDAV_PATH);
-                operation = new DetectAuthenticationMethodOperation(this.getApplicationContext(), webdav_url);
-            
-            } else {
-                // nothing we are going to handle
-                return START_NOT_STICKY;
-            }
-            
-            mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
-            //sendBroadcastNewOperation(target, operation);
-            
-            Message msg = mServiceHandler.obtainMessage();
-            msg.arg1 = startId;
-            mServiceHandler.sendMessage(msg);
-            
-        } catch (IllegalArgumentException e) {
-            Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
-            return START_NOT_STICKY;
-        }
-        
+        //Log.wtf(TAG, "onStartCommand init" );
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        mServiceHandler.sendMessage(msg);
+        //Log.wtf(TAG, "onStartCommand end" );
         return START_NOT_STICKY;
     }
 
@@ -167,6 +132,7 @@ public class OperationsService extends Service {
      */
     @Override
     public IBinder onBind(Intent intent) {
+        //Log.wtf(TAG, "onBind" );
         return mBinder;
     }
 
@@ -176,7 +142,7 @@ public class OperationsService extends Service {
      */
     @Override
     public boolean onUnbind(Intent intent) {
-        //((OperationsServiceBinder)mBinder).clearListeners();
+        ((OperationsServiceBinder)mBinder).clearListeners();
         return false;   // not accepting rebinding (default behaviour)
     }
 
@@ -191,7 +157,8 @@ public class OperationsService extends Service {
         /** 
          * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance 
          */
-        private Map<OnRemoteOperationListener, Handler> mBoundListeners = new HashMap<OnRemoteOperationListener, Handler>();
+        private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners = 
+                new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
         
         /**
          * Cancels an operation
@@ -216,7 +183,9 @@ public class OperationsService extends Service {
          * @param callbackHandler   {@link Handler} to access the listener without breaking Android threading protection.
          */
         public void addOperationListener (OnRemoteOperationListener listener, Handler callbackHandler) {
-            mBoundListeners.put(listener, callbackHandler);
+            synchronized (mBoundListeners) {
+                mBoundListeners.put(listener, callbackHandler);
+            }
         }
         
         
@@ -226,7 +195,9 @@ public class OperationsService extends Service {
          * @param listener      Object to notify about progress of transfer.    
          */
         public void removeOperationListener (OnRemoteOperationListener listener) {
-            mBoundListeners.remove(listener);
+            synchronized (mBoundListeners) {
+                mBoundListeners.remove(listener);
+            }
         }
 
 
@@ -239,6 +210,74 @@ public class OperationsService extends Service {
             return (!mPendingOperations.isEmpty());
         }
 
+
+        /**
+         * Creates and adds to the queue a new operation, as described by operationIntent
+         * 
+         * @param operationIntent       Intent describing a new operation to queue and execute.
+         * @return                      Identifier of the operation created, or -1 if failed.
+         */
+        public int newOperation(Intent operationIntent) {
+            RemoteOperation operation = null;
+            Target target = null;
+            try {
+                if (!operationIntent.hasExtra(EXTRA_ACCOUNT) && 
+                        !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+                    Log_OC.e(TAG, "Not enough information provided in intent");
+                    
+                } else {
+                    Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
+                    String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
+                    target = new Target(
+                            account, 
+                            (serverUrl == null) ? null : Uri.parse(serverUrl)
+                    );
+                    
+                    String action = operationIntent.getAction();
+                    if (action.equals(ACTION_CREATE_SHARE)) {  // Create Share
+                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                        Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
+                        if (remotePath.length() > 0) {
+                            operation = new CreateShareOperation(remotePath, ShareType.PUBLIC_LINK, 
+                                    "", false, "", 1, sendIntent);
+                        }
+                    } else if (action.equals(ACTION_UNSHARE)) {  // Unshare file
+                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                        if (remotePath.length() > 0) {
+                            operation = new UnshareLinkOperation(
+                                    remotePath, 
+                                    OperationsService.this);
+                        }
+                    } else if (action.equals(ACTION_DETECT_AUTHENTICATION_METHOD)) { 
+                        // Detect Authentication Method
+                        String webdav_url = 
+                                serverUrl + operationIntent.getStringExtra(EXTRA_WEBDAV_PATH);
+                        operation = new DetectAuthenticationMethodOperation(
+                                OperationsService.this, 
+                                webdav_url);
+                    }
+                }
+                    
+            } catch (IllegalArgumentException e) {
+                Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+                operation = null;
+            }
+
+            if (operation != null) {
+                mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+                startService(new Intent(OperationsService.this, OperationsService.class));
+                return operation.hashCode();
+                
+            } else {
+                return -1;
+            }
+        }
+
+        public RemoteOperationResult getOperationResultIfFinished(int mDetectAuthOpId) {
+            //Log_OC.wtf(TAG, "Searching result for operation with id " + mDetectAuthOpId);
+            return mOperationResults.remove(mDetectAuthOpId);
+        }
+
     }
     
     
@@ -271,6 +310,8 @@ public class OperationsService extends Service {
      */
     private void nextOperation() {
         
+        //Log.wtf(TAG, "nextOperation init" );
+        
         Pair<Target, RemoteOperation> next = null;
         synchronized(mPendingOperations) {
             next = mPendingOperations.peek();
@@ -326,6 +367,7 @@ public class OperationsService extends Service {
             } finally {
                 synchronized(mPendingOperations) {
                     mPendingOperations.poll();
+                    mOperationResults.put(mCurrentOperation.hashCode(), result);
                 }
             }
             
@@ -388,7 +430,9 @@ public class OperationsService extends Service {
      * @param operation         Finished operation.
      * @param result            Result of the operation.
      */
-    private void callbackOperationListeners(Target target, final RemoteOperation operation, final RemoteOperationResult result) {
+    private void callbackOperationListeners(
+            Target target, final RemoteOperation operation, final RemoteOperationResult result) {
+        int count = 0;
         Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
         while (listeners.hasNext()) {
             final OnRemoteOperationListener listener = listeners.next();
@@ -400,9 +444,10 @@ public class OperationsService extends Service {
                         listener.onRemoteOperationFinish(operation, result);
                     }
                 });
+                count += 1;
             }
         }
-            
+        Log_OC.d(TAG, "Called " + count + " listeners");
     }