ChooserActivity.java revision c6d5e3a406c0e80638304980bac13abaa703a9a0
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 boolean shouldGetResolvedFilter() { 405 return true; 406 } 407 408 @Override 409 public int getCount() { 410 int count = super.getCount(); 411 if (mServiceTargets != null) { 412 count += mServiceTargets.size(); 413 } 414 return count; 415 } 416 417 @Override 418 public TargetInfo getItem(int position) { 419 int offset = 0; 420 if (mServiceTargets != null) { 421 final int serviceTargetCount = mServiceTargets.size(); 422 if (position < serviceTargetCount) { 423 return mServiceTargets.get(position); 424 } 425 offset += serviceTargetCount; 426 } 427 return super.getItem(position - offset); 428 } 429 430 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 431 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 432 + " targets"); 433 for (int i = 0, N = targets.size(); i < N; i++) { 434 mServiceTargets.add(new ChooserTargetInfo(origTarget, targets.get(i))); 435 } 436 437 // TODO: Maintain sort by ranking scores. 438 439 notifyDataSetChanged(); 440 } 441 442 private void pruneServiceTargets() { 443 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 444 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 445 final ChooserTargetInfo cti = mServiceTargets.get(i); 446 if (!hasResolvedTarget(cti.getResolveInfo())) { 447 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 448 mServiceTargets.remove(i); 449 } 450 } 451 } 452 } 453 454 class ChooserTargetServiceConnection implements ServiceConnection { 455 private final DisplayResolveInfo mOriginalTarget; 456 457 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 458 @Override 459 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 460 final Message msg = Message.obtain(); 461 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 462 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 463 ChooserTargetServiceConnection.this); 464 mTargetResultHandler.sendMessage(msg); 465 } 466 }; 467 468 public ChooserTargetServiceConnection(DisplayResolveInfo dri) { 469 mOriginalTarget = dri; 470 } 471 472 @Override 473 public void onServiceConnected(ComponentName name, IBinder service) { 474 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 475 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 476 try { 477 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 478 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 479 } catch (RemoteException e) { 480 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 481 unbindService(this); 482 mServiceConnections.remove(this); 483 } 484 } 485 486 @Override 487 public void onServiceDisconnected(ComponentName name) { 488 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 489 unbindService(this); 490 mServiceConnections.remove(this); 491 } 492 493 @Override 494 public String toString() { 495 return mOriginalTarget.getResolveInfo().activityInfo.toString(); 496 } 497 } 498 499 static class ServiceResultInfo { 500 public final DisplayResolveInfo originalTarget; 501 public final List<ChooserTarget> resultTargets; 502 public final ChooserTargetServiceConnection connection; 503 504 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 505 ChooserTargetServiceConnection c) { 506 originalTarget = ot; 507 resultTargets = rt; 508 connection = c; 509 } 510 } 511} 512