ChooserActivity.java revision 2442841819f9554f9b5c8b9c147a51b04e50de4d
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.app; 18 19import android.app.Activity; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentSender; 24import android.content.ServiceConnection; 25import android.content.pm.ActivityInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.content.pm.ResolveInfo; 29import android.graphics.drawable.BitmapDrawable; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.IBinder; 34import android.os.Message; 35import android.os.Parcelable; 36import android.os.RemoteException; 37import android.os.UserHandle; 38import android.service.chooser.ChooserTarget; 39import android.service.chooser.ChooserTargetService; 40import android.service.chooser.IChooserTargetResult; 41import android.service.chooser.IChooserTargetService; 42import android.text.TextUtils; 43import android.util.Log; 44import android.util.Slog; 45import android.view.View; 46import android.view.ViewGroup; 47 48import java.util.ArrayList; 49import java.util.List; 50 51public class ChooserActivity extends ResolverActivity { 52 private static final String TAG = "ChooserActivity"; 53 54 private static final boolean DEBUG = false; 55 56 private static final int QUERY_TARGET_LIMIT = 5; 57 private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; 58 59 private Bundle mReplacementExtras; 60 private IntentSender mChosenComponentSender; 61 62 private ChooserTarget[] mCallerChooserTargets; 63 64 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); 65 66 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; 67 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; 68 69 private Handler mTargetResultHandler = new Handler() { 70 @Override 71 public void handleMessage(Message msg) { 72 switch (msg.what) { 73 case CHOOSER_TARGET_SERVICE_RESULT: 74 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); 75 if (isDestroyed()) break; 76 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; 77 if (!mServiceConnections.contains(sri.connection)) { 78 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection 79 + " returned after being removed from active connections." 80 + " Have you considered returning results faster?"); 81 break; 82 } 83 final ChooserListAdapter cla = (ChooserListAdapter) getAdapter(); 84 cla.addServiceResults(sri.originalTarget, sri.resultTargets); 85 unbindService(sri.connection); 86 mServiceConnections.remove(sri.connection); 87 break; 88 89 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT: 90 if (DEBUG) { 91 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); 92 } 93 unbindRemainingServices(); 94 break; 95 96 default: 97 super.handleMessage(msg); 98 } 99 } 100 }; 101 102 @Override 103 protected void onCreate(Bundle savedInstanceState) { 104 Intent intent = getIntent(); 105 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); 106 if (!(targetParcelable instanceof Intent)) { 107 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable); 108 finish(); 109 super.onCreate(null); 110 return; 111 } 112 Intent target = (Intent) targetParcelable; 113 if (target != null) { 114 modifyTargetIntent(target); 115 } 116 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); 117 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); 118 int defaultTitleRes = 0; 119 if (title == null) { 120 defaultTitleRes = com.android.internal.R.string.chooseActivity; 121 } 122 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS); 123 Intent[] initialIntents = null; 124 if (pa != null) { 125 initialIntents = new Intent[pa.length]; 126 for (int i=0; i<pa.length; i++) { 127 if (!(pa[i] instanceof Intent)) { 128 Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); 129 finish(); 130 super.onCreate(null); 131 return; 132 } 133 final Intent in = (Intent) pa[i]; 134 modifyTargetIntent(in); 135 initialIntents[i] = in; 136 } 137 } 138 139 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); 140 if (pa != null) { 141 final ChooserTarget[] targets = new ChooserTarget[pa.length]; 142 for (int i = 0; i < pa.length; i++) { 143 if (!(pa[i] instanceof ChooserTarget)) { 144 Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " + 145 pa[i]); 146 finish(); 147 super.onCreate(null); 148 return; 149 } 150 targets[i] = (ChooserTarget) pa[i]; 151 } 152 mCallerChooserTargets = targets; 153 } 154 mChosenComponentSender = intent.getParcelableExtra( 155 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); 156 setSafeForwardingMode(true); 157 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, 158 null, false); 159 } 160 161 @Override 162 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 163 Intent result = defIntent; 164 if (mReplacementExtras != null) { 165 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName); 166 if (replExtras != null) { 167 result = new Intent(defIntent); 168 result.putExtras(replExtras); 169 } 170 } 171 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER) 172 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { 173 result = Intent.createChooser(result, 174 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); 175 } 176 return result; 177 } 178 179 @Override 180 void onActivityStarted(TargetInfo cti) { 181 if (mChosenComponentSender != null) { 182 final ComponentName target = cti.getResolvedComponentName(); 183 if (target != null) { 184 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); 185 try { 186 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null); 187 } catch (IntentSender.SendIntentException e) { 188 Slog.e(TAG, "Unable to launch supplied IntentSender to report " 189 + "the chosen component: " + e); 190 } 191 } 192 } 193 } 194 195 @Override 196 int getLayoutResource() { 197 return com.android.internal.R.layout.chooser_grid; 198 } 199 200 @Override 201 boolean shouldGetActivityMetadata() { 202 return true; 203 } 204 205 private void modifyTargetIntent(Intent in) { 206 final String action = in.getAction(); 207 if (Intent.ACTION_SEND.equals(action) || 208 Intent.ACTION_SEND_MULTIPLE.equals(action)) { 209 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | 210 Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 211 } 212 } 213 214 void queryTargetServices(ChooserListAdapter adapter) { 215 final PackageManager pm = getPackageManager(); 216 int targetsToQuery = 0; 217 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { 218 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); 219 final ActivityInfo ai = dri.getResolveInfo().activityInfo; 220 final Bundle md = ai.metaData; 221 final String serviceName = md != null ? convertServiceName(ai.packageName, 222 md.getString(ChooserTargetService.META_DATA_NAME)) : null; 223 if (serviceName != null) { 224 final ComponentName serviceComponent = new ComponentName( 225 ai.packageName, serviceName); 226 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE) 227 .setComponent(serviceComponent); 228 229 if (DEBUG) { 230 Log.d(TAG, "queryTargets found target with service " + serviceComponent); 231 } 232 233 try { 234 final String perm = pm.getServiceInfo(serviceComponent, 0).permission; 235 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) { 236 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require" 237 + " permission " + ChooserTargetService.BIND_PERMISSION 238 + " - this service will not be queried for ChooserTargets." 239 + " add android:permission=\"" 240 + ChooserTargetService.BIND_PERMISSION + "\"" 241 + " to the <service> tag for " + serviceComponent 242 + " in the manifest."); 243 continue; 244 } 245 } catch (NameNotFoundException e) { 246 Log.e(TAG, "Could not look up service " + serviceComponent, e); 247 continue; 248 } 249 250 final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri); 251 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, 252 UserHandle.CURRENT)) { 253 if (DEBUG) { 254 Log.d(TAG, "Binding service connection for target " + dri 255 + " intent " + serviceIntent); 256 } 257 mServiceConnections.add(conn); 258 targetsToQuery++; 259 } 260 } 261 if (targetsToQuery >= QUERY_TARGET_LIMIT) { 262 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT); 263 break; 264 } 265 } 266 267 if (!mServiceConnections.isEmpty()) { 268 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for " 269 + WATCHDOG_TIMEOUT_MILLIS + "ms"); 270 mTargetResultHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, 271 WATCHDOG_TIMEOUT_MILLIS); 272 } 273 } 274 275 private String convertServiceName(String packageName, String serviceName) { 276 if (TextUtils.isEmpty(serviceName)) { 277 return null; 278 } 279 280 final String fullName; 281 if (serviceName.startsWith(".")) { 282 // Relative to the app package. Prepend the app package name. 283 fullName = packageName + serviceName; 284 } else if (serviceName.indexOf('.') >= 0) { 285 // Fully qualified package name. 286 fullName = serviceName; 287 } else { 288 fullName = null; 289 } 290 return fullName; 291 } 292 293 void unbindRemainingServices() { 294 if (DEBUG) { 295 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left"); 296 } 297 for (int i = 0, N = mServiceConnections.size(); i < N; i++) { 298 final ChooserTargetServiceConnection conn = mServiceConnections.get(i); 299 if (DEBUG) Log.d(TAG, "unbinding " + conn); 300 unbindService(conn); 301 } 302 mServiceConnections.clear(); 303 mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 304 } 305 306 @Override 307 ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, 308 List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { 309 final ChooserListAdapter adapter = new ChooserListAdapter(context, initialIntents, rList, 310 launchedFromUid, filterLastUsed); 311 if (DEBUG) Log.d(TAG, "Adapter created; querying services"); 312 queryTargetServices(adapter); 313 return adapter; 314 } 315 316 class ChooserTargetInfo implements TargetInfo { 317 private final TargetInfo mSourceInfo; 318 private final ChooserTarget mChooserTarget; 319 private final Drawable mDisplayIcon; 320 321 public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) { 322 mSourceInfo = sourceInfo; 323 mChooserTarget = chooserTarget; 324 mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); 325 } 326 327 @Override 328 public Intent getResolvedIntent() { 329 final Intent targetIntent = mChooserTarget.getIntent(); 330 return targetIntent != null ? targetIntent : mSourceInfo.getResolvedIntent(); 331 } 332 333 @Override 334 public ComponentName getResolvedComponentName() { 335 return mSourceInfo.getResolvedComponentName(); 336 } 337 338 @Override 339 public boolean start(Activity activity, Bundle options) { 340 return mChooserTarget.sendIntent(activity, mSourceInfo.getResolvedIntent()); 341 } 342 343 @Override 344 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 345 return mChooserTarget.sendIntentAsCaller(activity, mSourceInfo.getResolvedIntent(), 346 userId); 347 } 348 349 @Override 350 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 351 return mChooserTarget.sendIntentAsUser(activity, mSourceInfo.getResolvedIntent(), user); 352 } 353 354 @Override 355 public ResolveInfo getResolveInfo() { 356 return mSourceInfo.getResolveInfo(); 357 } 358 359 @Override 360 public CharSequence getDisplayLabel() { 361 return mChooserTarget.getTitle(); 362 } 363 364 @Override 365 public CharSequence getExtendedInfo() { 366 return mSourceInfo.getExtendedInfo(); 367 } 368 369 @Override 370 public Drawable getDisplayIcon() { 371 return mDisplayIcon; 372 } 373 } 374 375 public class ChooserListAdapter extends ResolveListAdapter { 376 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); 377 378 public ChooserListAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, 379 int launchedFromUid, boolean filterLastUsed) { 380 super(context, initialIntents, rList, launchedFromUid, filterLastUsed); 381 } 382 383 @Override 384 public boolean showsExtendedInfo(TargetInfo info) { 385 // Reserve space to show extended info if any one of the items in the adapter has 386 // extended info. This keeps grid item sizes uniform. 387 return hasExtendedInfo(); 388 } 389 390 @Override 391 public View createView(ViewGroup parent) { 392 return mInflater.inflate( 393 com.android.internal.R.layout.resolve_grid_item, parent, false); 394 } 395 396 @Override 397 public void onListRebuilt() { 398 if (mServiceTargets != null) { 399 pruneServiceTargets(); 400 } 401 } 402 403 @Override 404 public int getCount() { 405 int count = super.getCount(); 406 if (mServiceTargets != null) { 407 count += mServiceTargets.size(); 408 } 409 return count; 410 } 411 412 @Override 413 public TargetInfo getItem(int position) { 414 int offset = 0; 415 if (mServiceTargets != null) { 416 final int serviceTargetCount = mServiceTargets.size(); 417 if (position < serviceTargetCount) { 418 return mServiceTargets.get(position); 419 } 420 offset += serviceTargetCount; 421 } 422 return super.getItem(position - offset); 423 } 424 425 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 426 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 427 + " targets"); 428 for (int i = 0, N = targets.size(); i < N; i++) { 429 mServiceTargets.add(new ChooserTargetInfo(origTarget, targets.get(i))); 430 } 431 432 // TODO: Maintain sort by ranking scores. 433 434 notifyDataSetChanged(); 435 } 436 437 private void pruneServiceTargets() { 438 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 439 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 440 final ChooserTargetInfo cti = mServiceTargets.get(i); 441 if (!hasResolvedTarget(cti.getResolveInfo())) { 442 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 443 mServiceTargets.remove(i); 444 } 445 } 446 } 447 } 448 449 class ChooserTargetServiceConnection implements ServiceConnection { 450 private final DisplayResolveInfo mOriginalTarget; 451 452 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 453 @Override 454 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 455 final Message msg = Message.obtain(); 456 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 457 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 458 ChooserTargetServiceConnection.this); 459 mTargetResultHandler.sendMessage(msg); 460 } 461 }; 462 463 public ChooserTargetServiceConnection(DisplayResolveInfo dri) { 464 mOriginalTarget = dri; 465 } 466 467 @Override 468 public void onServiceConnected(ComponentName name, IBinder service) { 469 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 470 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 471 try { 472 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 473 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 474 } catch (RemoteException e) { 475 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 476 unbindService(this); 477 mServiceConnections.remove(this); 478 } 479 } 480 481 @Override 482 public void onServiceDisconnected(ComponentName name) { 483 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 484 unbindService(this); 485 mServiceConnections.remove(this); 486 } 487 488 @Override 489 public String toString() { 490 return mOriginalTarget.getResolveInfo().activityInfo.toString(); 491 } 492 } 493 494 static class ServiceResultInfo { 495 public final DisplayResolveInfo originalTarget; 496 public final List<ChooserTarget> resultTargets; 497 public final ChooserTargetServiceConnection connection; 498 499 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 500 ChooserTargetServiceConnection c) { 501 originalTarget = ot; 502 resultTargets = rt; 503 connection = c; 504 } 505 } 506} 507