OSDN Git Service

Handle single-choice ChooserActivities and bound ChooserTargetServices
authorAdam Powell <adamp@google.com>
Wed, 9 Sep 2015 00:01:49 +0000 (17:01 -0700)
committerAdam Powell <adamp@google.com>
Wed, 9 Sep 2015 16:50:30 +0000 (16:50 +0000)
Fix cases where we could try to unbind from a ChooserTargetService
that is not connected. This could happen if we still had stale replies
coming back after the activity was destroyed.

Always offer users an explicit choice in ChooserActivity, don't
auto-start a single option.

Make sure we don't allow a wedged ChooserTargetService to hold a hard
reference to the ChooserActivity via its internal result callback.

Bug 23152483

Change-Id: I7c8b1fc9559dcd477702ef582011b088b07d646b

core/java/com/android/internal/app/ChooserActivity.java
core/java/com/android/internal/app/ResolverActivity.java

index 5fc8c7a..5b19edf 100644 (file)
@@ -104,6 +104,7 @@ public class ChooserActivity extends ResolverActivity {
                                 sri.resultTargets);
                     }
                     unbindService(sri.connection);
+                    sri.connection.destroy();
                     mServiceConnections.remove(sri.connection);
                     if (mServiceConnections.isEmpty()) {
                         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
@@ -208,6 +209,8 @@ public class ChooserActivity extends ResolverActivity {
             mRefinementResultReceiver.destroy();
             mRefinementResultReceiver = null;
         }
+        unbindRemainingServices();
+        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
     }
 
     @Override
@@ -265,6 +268,11 @@ public class ChooserActivity extends ResolverActivity {
         return true;
     }
 
+    @Override
+    boolean shouldAutoLaunchSingleChoice() {
+        return false;
+    }
+
     private void modifyTargetIntent(Intent in) {
         final String action = in.getAction();
         if (Intent.ACTION_SEND.equals(action) ||
@@ -371,7 +379,8 @@ public class ChooserActivity extends ResolverActivity {
                     continue;
                 }
 
-                final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri);
+                final ChooserTargetServiceConnection conn =
+                        new ChooserTargetServiceConnection(this, dri);
                 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
                         UserHandle.CURRENT)) {
                     if (DEBUG) {
@@ -425,6 +434,7 @@ public class ChooserActivity extends ResolverActivity {
             final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
             if (DEBUG) Log.d(TAG, "unbinding " + conn);
             unbindService(conn);
+            conn.destroy();
         }
         mServiceConnections.clear();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
@@ -1024,54 +1034,93 @@ public class ChooserActivity extends ResolverActivity {
         }
     }
 
-    class ChooserTargetServiceConnection implements ServiceConnection {
+    static class ChooserTargetServiceConnection implements ServiceConnection {
         private final DisplayResolveInfo mOriginalTarget;
+        private ComponentName mConnectedComponent;
+        private ChooserActivity mChooserActivity;
+        private final Object mLock = new Object();
 
         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
             @Override
             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
-                filterServiceTargets(mOriginalTarget.getResolveInfo().activityInfo.packageName,
-                        targets);
-                final Message msg = Message.obtain();
-                msg.what = CHOOSER_TARGET_SERVICE_RESULT;
-                msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
-                        ChooserTargetServiceConnection.this);
-                mChooserHandler.sendMessage(msg);
+                synchronized (mLock) {
+                    if (mChooserActivity == null) {
+                        Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
+                                + mConnectedComponent + "; ignoring...");
+                        return;
+                    }
+                    mChooserActivity.filterServiceTargets(
+                            mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
+                    final Message msg = Message.obtain();
+                    msg.what = CHOOSER_TARGET_SERVICE_RESULT;
+                    msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
+                            ChooserTargetServiceConnection.this);
+                    mChooserActivity.mChooserHandler.sendMessage(msg);
+                }
             }
         };
 
-        public ChooserTargetServiceConnection(DisplayResolveInfo dri) {
+        public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
+                DisplayResolveInfo dri) {
+            mChooserActivity = chooserActivity;
             mOriginalTarget = dri;
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
-            final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
-            try {
-                icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
-                        mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
-                unbindService(this);
-                mServiceConnections.remove(this);
+            synchronized (mLock) {
+                if (mChooserActivity == null) {
+                    Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
+                    return;
+                }
+
+                final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
+                try {
+                    icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
+                            mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
+                    mChooserActivity.unbindService(this);
+                    destroy();
+                    mChooserActivity.mServiceConnections.remove(this);
+                }
             }
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
-            unbindService(this);
-            mServiceConnections.remove(this);
-            if (mServiceConnections.isEmpty()) {
-                mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
-                sendVoiceChoicesIfNeeded();
+            synchronized (mLock) {
+                if (mChooserActivity == null) {
+                    Log.e(TAG,
+                            "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
+                    return;
+                }
+
+                mChooserActivity.unbindService(this);
+                destroy();
+                mChooserActivity.mServiceConnections.remove(this);
+                if (mChooserActivity.mServiceConnections.isEmpty()) {
+                    mChooserActivity.mChooserHandler.removeMessages(
+                            CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+                    mChooserActivity.sendVoiceChoicesIfNeeded();
+                }
+                mConnectedComponent = null;
+            }
+        }
+
+        public void destroy() {
+            synchronized (mLock) {
+                mChooserActivity = null;
             }
         }
 
         @Override
         public String toString() {
-            return mOriginalTarget.getResolveInfo().activityInfo.toString();
+            return "ChooserTargetServiceConnection{service="
+                    + mConnectedComponent + ", activity="
+                    + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
         }
     }
 
index 7dd3bed..9272193 100644 (file)
@@ -234,7 +234,9 @@ public class ResolverActivity extends Activity {
 
         mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage);
 
-        configureContentView(mIntents, initialIntents, rList, alwaysUseOption);
+        if (configureContentView(mIntents, initialIntents, rList, alwaysUseOption)) {
+            return;
+        }
 
         // Prevent the Resolver window from becoming the top fullscreen window and thus from taking
         // control of the system bars.
@@ -794,6 +796,10 @@ public class ResolverActivity extends Activity {
         return false;
     }
 
+    boolean shouldAutoLaunchSingleChoice() {
+        return true;
+    }
+
     void showAppDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
@@ -808,7 +814,10 @@ public class ResolverActivity extends Activity {
                 launchedFromUid, filterLastUsed);
     }
 
-    void configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
+    /**
+     * Returns true if the activity is finishing and creation should halt
+     */
+    boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
             List<ResolveInfo> rList, boolean alwaysUseOption) {
         // The last argument of createAdapter is whether to do special handling
         // of the last used choice to highlight it in the list.  We need to always
@@ -828,7 +837,9 @@ public class ResolverActivity extends Activity {
         mAlwaysUseOption = alwaysUseOption;
 
         int count = mAdapter.getUnfilteredCount();
-        if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) {
+        if ((!shouldAutoLaunchSingleChoice() && count > 0)
+                || count > 1
+                || (count == 1 && mAdapter.getOtherProfile() != null)) {
             setContentView(layoutId);
             mAdapterView = (AbsListView) findViewById(R.id.resolver_list);
             onPrepareAdapterView(mAdapterView, mAdapter, alwaysUseOption);
@@ -837,7 +848,7 @@ public class ResolverActivity extends Activity {
             mPackageMonitor.unregister();
             mRegistered = false;
             finish();
-            return;
+            return true;
         } else {
             setContentView(R.layout.resolver_list);
 
@@ -847,6 +858,7 @@ public class ResolverActivity extends Activity {
             mAdapterView = (AbsListView) findViewById(R.id.resolver_list);
             mAdapterView.setVisibility(View.GONE);
         }
+        return false;
     }
 
     void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,