Handle single-choice ChooserActivities and bound ChooserTargetServices

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
(cherry picked from commit 9761ab2a64)
This commit is contained in:
Adam Powell
2015-09-08 17:01:49 -07:00
parent bfc584df68
commit 39e94ebe22
2 changed files with 89 additions and 28 deletions

View 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() + "}";
}
}

View 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,