Home | History | Annotate | Download | only in app
      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 
     17 package com.android.internal.app;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.annotation.NonNull;
     23 import android.app.Activity;
     24 import android.app.ActivityManager;
     25 import android.app.usage.UsageStatsManager;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentSender;
     30 import android.content.IntentSender.SendIntentException;
     31 import android.content.ServiceConnection;
     32 import android.content.SharedPreferences;
     33 import android.content.pm.ActivityInfo;
     34 import android.content.pm.LabeledIntent;
     35 import android.content.pm.PackageManager;
     36 import android.content.pm.PackageManager.NameNotFoundException;
     37 import android.content.pm.ResolveInfo;
     38 import android.database.DataSetObserver;
     39 import android.graphics.Color;
     40 import android.graphics.drawable.Drawable;
     41 import android.graphics.drawable.Icon;
     42 import android.os.Bundle;
     43 import android.os.Environment;
     44 import android.os.Handler;
     45 import android.os.IBinder;
     46 import android.os.Message;
     47 import android.os.Parcelable;
     48 import android.os.Process;
     49 import android.os.RemoteException;
     50 import android.os.ResultReceiver;
     51 import android.os.UserHandle;
     52 import android.os.UserManager;
     53 import android.os.storage.StorageManager;
     54 import android.service.chooser.ChooserTarget;
     55 import android.service.chooser.ChooserTargetService;
     56 import android.service.chooser.IChooserTargetResult;
     57 import android.service.chooser.IChooserTargetService;
     58 import android.text.TextUtils;
     59 import android.util.FloatProperty;
     60 import android.util.Log;
     61 import android.util.Slog;
     62 import android.view.LayoutInflater;
     63 import android.view.View;
     64 import android.view.View.MeasureSpec;
     65 import android.view.View.OnClickListener;
     66 import android.view.View.OnLongClickListener;
     67 import android.view.ViewGroup;
     68 import android.view.ViewGroup.LayoutParams;
     69 import android.view.animation.AnimationUtils;
     70 import android.view.animation.Interpolator;
     71 import android.widget.AbsListView;
     72 import android.widget.BaseAdapter;
     73 import android.widget.LinearLayout;
     74 import android.widget.ListView;
     75 import android.widget.Space;
     76 
     77 import com.android.internal.R;
     78 import com.android.internal.annotations.VisibleForTesting;
     79 import com.android.internal.app.ResolverActivity;
     80 import com.android.internal.app.ResolverActivity.TargetInfo;
     81 import com.android.internal.logging.MetricsLogger;
     82 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     83 import com.google.android.collect.Lists;
     84 
     85 import java.io.File;
     86 import java.util.ArrayList;
     87 import java.util.Collections;
     88 import java.util.Comparator;
     89 import java.util.List;
     90 
     91 public class ChooserActivity extends ResolverActivity {
     92     private static final String TAG = "ChooserActivity";
     93 
     94     /**
     95      * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
     96      * in onStop when launched in a new task. If this extra is set to true, we do not finish
     97      * ourselves when onStop gets called.
     98      */
     99     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
    100             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
    101 
    102     private static final boolean DEBUG = false;
    103 
    104     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
    105     private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
    106 
    107     private Bundle mReplacementExtras;
    108     private IntentSender mChosenComponentSender;
    109     private IntentSender mRefinementIntentSender;
    110     private RefinementResultReceiver mRefinementResultReceiver;
    111     private ChooserTarget[] mCallerChooserTargets;
    112     private ComponentName[] mFilteredComponentNames;
    113 
    114     private Intent mReferrerFillInIntent;
    115 
    116     private long mChooserShownTime;
    117     protected boolean mIsSuccessfullySelected;
    118 
    119     private ChooserListAdapter mChooserListAdapter;
    120     private ChooserRowAdapter mChooserRowAdapter;
    121 
    122     private SharedPreferences mPinnedSharedPrefs;
    123     private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
    124     private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
    125     private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
    126     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
    127 
    128     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
    129 
    130     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
    131     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
    132 
    133     private final Handler mChooserHandler = new Handler() {
    134         @Override
    135         public void handleMessage(Message msg) {
    136             switch (msg.what) {
    137                 case CHOOSER_TARGET_SERVICE_RESULT:
    138                     if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
    139                     if (isDestroyed()) break;
    140                     final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
    141                     if (!mServiceConnections.contains(sri.connection)) {
    142                         Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
    143                                 + " returned after being removed from active connections."
    144                                 + " Have you considered returning results faster?");
    145                         break;
    146                     }
    147                     if (sri.resultTargets != null) {
    148                         mChooserListAdapter.addServiceResults(sri.originalTarget,
    149                                 sri.resultTargets);
    150                     }
    151                     unbindService(sri.connection);
    152                     sri.connection.destroy();
    153                     mServiceConnections.remove(sri.connection);
    154                     if (mServiceConnections.isEmpty()) {
    155                         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
    156                         sendVoiceChoicesIfNeeded();
    157                         mChooserListAdapter.setShowServiceTargets(true);
    158                     }
    159                     break;
    160 
    161                 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
    162                     if (DEBUG) {
    163                         Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
    164                     }
    165                     unbindRemainingServices();
    166                     sendVoiceChoicesIfNeeded();
    167                     mChooserListAdapter.setShowServiceTargets(true);
    168                     break;
    169 
    170                 default:
    171                     super.handleMessage(msg);
    172             }
    173         }
    174     };
    175 
    176     @Override
    177     protected void onCreate(Bundle savedInstanceState) {
    178         final long intentReceivedTime = System.currentTimeMillis();
    179         mIsSuccessfullySelected = false;
    180         Intent intent = getIntent();
    181         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
    182         if (!(targetParcelable instanceof Intent)) {
    183             Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
    184             finish();
    185             super.onCreate(null);
    186             return;
    187         }
    188         Intent target = (Intent) targetParcelable;
    189         if (target != null) {
    190             modifyTargetIntent(target);
    191         }
    192         Parcelable[] targetsParcelable
    193                 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
    194         if (targetsParcelable != null) {
    195             final boolean offset = target == null;
    196             Intent[] additionalTargets =
    197                     new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
    198             for (int i = 0; i < targetsParcelable.length; i++) {
    199                 if (!(targetsParcelable[i] instanceof Intent)) {
    200                     Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
    201                             + targetsParcelable[i]);
    202                     finish();
    203                     super.onCreate(null);
    204                     return;
    205                 }
    206                 final Intent additionalTarget = (Intent) targetsParcelable[i];
    207                 if (i == 0 && target == null) {
    208                     target = additionalTarget;
    209                     modifyTargetIntent(target);
    210                 } else {
    211                     additionalTargets[offset ? i - 1 : i] = additionalTarget;
    212                     modifyTargetIntent(additionalTarget);
    213                 }
    214             }
    215             setAdditionalTargets(additionalTargets);
    216         }
    217 
    218         mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
    219         CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
    220         int defaultTitleRes = 0;
    221         if (title == null) {
    222             defaultTitleRes = com.android.internal.R.string.chooseActivity;
    223         }
    224         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
    225         Intent[] initialIntents = null;
    226         if (pa != null) {
    227             initialIntents = new Intent[pa.length];
    228             for (int i=0; i<pa.length; i++) {
    229                 if (!(pa[i] instanceof Intent)) {
    230                     Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
    231                     finish();
    232                     super.onCreate(null);
    233                     return;
    234                 }
    235                 final Intent in = (Intent) pa[i];
    236                 modifyTargetIntent(in);
    237                 initialIntents[i] = in;
    238             }
    239         }
    240 
    241         mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
    242 
    243         mChosenComponentSender = intent.getParcelableExtra(
    244                 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
    245         mRefinementIntentSender = intent.getParcelableExtra(
    246                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
    247         setSafeForwardingMode(true);
    248 
    249         pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
    250         if (pa != null) {
    251             ComponentName[] names = new ComponentName[pa.length];
    252             for (int i = 0; i < pa.length; i++) {
    253                 if (!(pa[i] instanceof ComponentName)) {
    254                     Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
    255                     names = null;
    256                     break;
    257                 }
    258                 names[i] = (ComponentName) pa[i];
    259             }
    260             mFilteredComponentNames = names;
    261         }
    262 
    263         pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
    264         if (pa != null) {
    265             ChooserTarget[] targets = new ChooserTarget[pa.length];
    266             for (int i = 0; i < pa.length; i++) {
    267                 if (!(pa[i] instanceof ChooserTarget)) {
    268                     Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
    269                     targets = null;
    270                     break;
    271                 }
    272                 targets[i] = (ChooserTarget) pa[i];
    273             }
    274             mCallerChooserTargets = targets;
    275         }
    276 
    277         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
    278         setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
    279         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
    280                 null, false);
    281 
    282         MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
    283 
    284         mChooserShownTime = System.currentTimeMillis();
    285         final long systemCost = mChooserShownTime - intentReceivedTime;
    286         MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
    287         if (DEBUG) {
    288             Log.d(TAG, "System Time Cost is " + systemCost);
    289         }
    290     }
    291 
    292     static SharedPreferences getPinnedSharedPrefs(Context context) {
    293         // The code below is because in the android:ui process, no one can hear you scream.
    294         // The package info in the context isn't initialized in the way it is for normal apps,
    295         // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
    296         // build the path manually below using the same policy that appears in ContextImpl.
    297         // This fails silently under the hood if there's a problem, so if we find ourselves in
    298         // the case where we don't have access to credential encrypted storage we just won't
    299         // have our pinned target info.
    300         final File prefsFile = new File(new File(
    301                 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
    302                         context.getUserId(), context.getPackageName()),
    303                 "shared_prefs"),
    304                 PINNED_SHARED_PREFS_NAME + ".xml");
    305         return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
    306     }
    307 
    308     @Override
    309     protected void onDestroy() {
    310         super.onDestroy();
    311         if (mRefinementResultReceiver != null) {
    312             mRefinementResultReceiver.destroy();
    313             mRefinementResultReceiver = null;
    314         }
    315         unbindRemainingServices();
    316         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
    317     }
    318 
    319     @Override
    320     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
    321         Intent result = defIntent;
    322         if (mReplacementExtras != null) {
    323             final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
    324             if (replExtras != null) {
    325                 result = new Intent(defIntent);
    326                 result.putExtras(replExtras);
    327             }
    328         }
    329         if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
    330                 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
    331             result = Intent.createChooser(result,
    332                     getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
    333 
    334             // Don't auto-launch single intents if the intent is being forwarded. This is done
    335             // because automatically launching a resolving application as a response to the user
    336             // action of switching accounts is pretty unexpected.
    337             result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
    338         }
    339         return result;
    340     }
    341 
    342     @Override
    343     public void onActivityStarted(TargetInfo cti) {
    344         if (mChosenComponentSender != null) {
    345             final ComponentName target = cti.getResolvedComponentName();
    346             if (target != null) {
    347                 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
    348                 try {
    349                     mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
    350                 } catch (IntentSender.SendIntentException e) {
    351                     Slog.e(TAG, "Unable to launch supplied IntentSender to report "
    352                             + "the chosen component: " + e);
    353                 }
    354             }
    355         }
    356     }
    357 
    358     @Override
    359     public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
    360         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
    361         mChooserListAdapter = (ChooserListAdapter) adapter;
    362         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
    363             mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
    364         }
    365         mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
    366         mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
    367         adapterView.setAdapter(mChooserRowAdapter);
    368         if (listView != null) {
    369             listView.setItemsCanFocus(true);
    370         }
    371     }
    372 
    373     @Override
    374     public int getLayoutResource() {
    375         return R.layout.chooser_grid;
    376     }
    377 
    378     @Override
    379     public boolean shouldGetActivityMetadata() {
    380         return true;
    381     }
    382 
    383     @Override
    384     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    385         // Note that this is only safe because the Intent handled by the ChooserActivity is
    386         // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
    387         // method can not be replaced in the ResolverActivity whole hog.
    388         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
    389                 super.shouldAutoLaunchSingleChoice(target));
    390     }
    391 
    392     @Override
    393     public void showTargetDetails(ResolveInfo ri) {
    394         if (ri == null) {
    395             return;
    396         }
    397 
    398         ComponentName name = ri.activityInfo.getComponentName();
    399         boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
    400         ResolverTargetActionsDialogFragment f =
    401                 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
    402                         name, pinned);
    403         f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
    404     }
    405 
    406     private void modifyTargetIntent(Intent in) {
    407         final String action = in.getAction();
    408         if (Intent.ACTION_SEND.equals(action) ||
    409                 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
    410             in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
    411                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    412         }
    413     }
    414 
    415     @Override
    416     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
    417         if (mRefinementIntentSender != null) {
    418             final Intent fillIn = new Intent();
    419             final List<Intent> sourceIntents = target.getAllSourceIntents();
    420             if (!sourceIntents.isEmpty()) {
    421                 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
    422                 if (sourceIntents.size() > 1) {
    423                     final Intent[] alts = new Intent[sourceIntents.size() - 1];
    424                     for (int i = 1, N = sourceIntents.size(); i < N; i++) {
    425                         alts[i - 1] = sourceIntents.get(i);
    426                     }
    427                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
    428                 }
    429                 if (mRefinementResultReceiver != null) {
    430                     mRefinementResultReceiver.destroy();
    431                 }
    432                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
    433                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
    434                         mRefinementResultReceiver);
    435                 try {
    436                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
    437                     return false;
    438                 } catch (SendIntentException e) {
    439                     Log.e(TAG, "Refinement IntentSender failed to send", e);
    440                 }
    441             }
    442         }
    443         updateModelAndChooserCounts(target);
    444         return super.onTargetSelected(target, alwaysCheck);
    445     }
    446 
    447     @Override
    448     public void startSelected(int which, boolean always, boolean filtered) {
    449         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
    450         super.startSelected(which, always, filtered);
    451 
    452         if (mChooserListAdapter != null) {
    453             // Log the index of which type of target the user picked.
    454             // Lower values mean the ranking was better.
    455             int cat = 0;
    456             int value = which;
    457             switch (mChooserListAdapter.getPositionTargetType(which)) {
    458                 case ChooserListAdapter.TARGET_CALLER:
    459                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
    460                     break;
    461                 case ChooserListAdapter.TARGET_SERVICE:
    462                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
    463                     value -= mChooserListAdapter.getCallerTargetCount();
    464                     break;
    465                 case ChooserListAdapter.TARGET_STANDARD:
    466                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
    467                     value -= mChooserListAdapter.getCallerTargetCount()
    468                             + mChooserListAdapter.getServiceTargetCount();
    469                     break;
    470             }
    471 
    472             if (cat != 0) {
    473                 MetricsLogger.action(this, cat, value);
    474             }
    475 
    476             if (mIsSuccessfullySelected) {
    477                 if (DEBUG) {
    478                     Log.d(TAG, "User Selection Time Cost is " + selectionCost);
    479                     Log.d(TAG, "position of selected app/service/caller is " +
    480                             Integer.toString(value));
    481                 }
    482                 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
    483                         (int) selectionCost);
    484                 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
    485             }
    486         }
    487     }
    488 
    489     void queryTargetServices(ChooserListAdapter adapter) {
    490         final PackageManager pm = getPackageManager();
    491         int targetsToQuery = 0;
    492         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
    493             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
    494             if (adapter.getScore(dri) == 0) {
    495                 // A score of 0 means the app hasn't been used in some time;
    496                 // don't query it as it's not likely to be relevant.
    497                 continue;
    498             }
    499             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
    500             final Bundle md = ai.metaData;
    501             final String serviceName = md != null ? convertServiceName(ai.packageName,
    502                     md.getString(ChooserTargetService.META_DATA_NAME)) : null;
    503             if (serviceName != null) {
    504                 final ComponentName serviceComponent = new ComponentName(
    505                         ai.packageName, serviceName);
    506                 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
    507                         .setComponent(serviceComponent);
    508 
    509                 if (DEBUG) {
    510                     Log.d(TAG, "queryTargets found target with service " + serviceComponent);
    511                 }
    512 
    513                 try {
    514                     final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
    515                     if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
    516                         Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
    517                                 + " permission " + ChooserTargetService.BIND_PERMISSION
    518                                 + " - this service will not be queried for ChooserTargets."
    519                                 + " add android:permission=\""
    520                                 + ChooserTargetService.BIND_PERMISSION + "\""
    521                                 + " to the <service> tag for " + serviceComponent
    522                                 + " in the manifest.");
    523                         continue;
    524                     }
    525                 } catch (NameNotFoundException e) {
    526                     Log.e(TAG, "Could not look up service " + serviceComponent
    527                             + "; component name not found");
    528                     continue;
    529                 }
    530 
    531                 final ChooserTargetServiceConnection conn =
    532                         new ChooserTargetServiceConnection(this, dri);
    533 
    534                 // Explicitly specify Process.myUserHandle instead of calling bindService
    535                 // to avoid the warning from calling from the system process without an explicit
    536                 // user handle
    537                 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
    538                         Process.myUserHandle())) {
    539                     if (DEBUG) {
    540                         Log.d(TAG, "Binding service connection for target " + dri
    541                                 + " intent " + serviceIntent);
    542                     }
    543                     mServiceConnections.add(conn);
    544                     targetsToQuery++;
    545                 }
    546             }
    547             if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
    548                 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
    549                         + QUERY_TARGET_SERVICE_LIMIT);
    550                 break;
    551             }
    552         }
    553 
    554         if (!mServiceConnections.isEmpty()) {
    555             if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
    556                     + WATCHDOG_TIMEOUT_MILLIS + "ms");
    557             mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
    558                     WATCHDOG_TIMEOUT_MILLIS);
    559         } else {
    560             sendVoiceChoicesIfNeeded();
    561         }
    562     }
    563 
    564     private String convertServiceName(String packageName, String serviceName) {
    565         if (TextUtils.isEmpty(serviceName)) {
    566             return null;
    567         }
    568 
    569         final String fullName;
    570         if (serviceName.startsWith(".")) {
    571             // Relative to the app package. Prepend the app package name.
    572             fullName = packageName + serviceName;
    573         } else if (serviceName.indexOf('.') >= 0) {
    574             // Fully qualified package name.
    575             fullName = serviceName;
    576         } else {
    577             fullName = null;
    578         }
    579         return fullName;
    580     }
    581 
    582     void unbindRemainingServices() {
    583         if (DEBUG) {
    584             Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
    585         }
    586         for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
    587             final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
    588             if (DEBUG) Log.d(TAG, "unbinding " + conn);
    589             unbindService(conn);
    590             conn.destroy();
    591         }
    592         mServiceConnections.clear();
    593         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
    594     }
    595 
    596     public void onSetupVoiceInteraction() {
    597         // Do nothing. We'll send the voice stuff ourselves.
    598     }
    599 
    600     void updateModelAndChooserCounts(TargetInfo info) {
    601         if (info != null) {
    602             final ResolveInfo ri = info.getResolveInfo();
    603             Intent targetIntent = getTargetIntent();
    604             if (ri != null && ri.activityInfo != null && targetIntent != null) {
    605                 if (mAdapter != null) {
    606                     mAdapter.updateModel(info.getResolvedComponentName());
    607                     mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
    608                             targetIntent.getAction());
    609                 }
    610                 if (DEBUG) {
    611                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
    612                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
    613                 }
    614             } else if(DEBUG) {
    615                 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
    616             }
    617         }
    618         mIsSuccessfullySelected = true;
    619     }
    620 
    621     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
    622         if (mRefinementResultReceiver != null) {
    623             mRefinementResultReceiver.destroy();
    624             mRefinementResultReceiver = null;
    625         }
    626         if (selectedTarget == null) {
    627             Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
    628         } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
    629             Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
    630                     + " cannot match refined source intent " + matchingIntent);
    631         } else {
    632             TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
    633             if (super.onTargetSelected(clonedTarget, false)) {
    634                 updateModelAndChooserCounts(clonedTarget);
    635                 finish();
    636                 return;
    637             }
    638         }
    639         onRefinementCanceled();
    640     }
    641 
    642     void onRefinementCanceled() {
    643         if (mRefinementResultReceiver != null) {
    644             mRefinementResultReceiver.destroy();
    645             mRefinementResultReceiver = null;
    646         }
    647         finish();
    648     }
    649 
    650     boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
    651         final List<Intent> targetIntents = target.getAllSourceIntents();
    652         for (int i = 0, N = targetIntents.size(); i < N; i++) {
    653             final Intent targetIntent = targetIntents.get(i);
    654             if (targetIntent.filterEquals(matchingIntent)) {
    655                 return true;
    656             }
    657         }
    658         return false;
    659     }
    660 
    661     void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
    662         if (targets == null) {
    663             return;
    664         }
    665 
    666         final PackageManager pm = getPackageManager();
    667         for (int i = targets.size() - 1; i >= 0; i--) {
    668             final ChooserTarget target = targets.get(i);
    669             final ComponentName targetName = target.getComponentName();
    670             if (packageName != null && packageName.equals(targetName.getPackageName())) {
    671                 // Anything from the original target's package is fine.
    672                 continue;
    673             }
    674 
    675             boolean remove;
    676             try {
    677                 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
    678                 remove = !ai.exported || ai.permission != null;
    679             } catch (NameNotFoundException e) {
    680                 Log.e(TAG, "Target " + target + " returned by " + packageName
    681                         + " component not found");
    682                 remove = true;
    683             }
    684 
    685             if (remove) {
    686                 targets.remove(i);
    687             }
    688         }
    689     }
    690 
    691     public class ChooserListController extends ResolverListController {
    692         public ChooserListController(Context context,
    693                 PackageManager pm,
    694                 Intent targetIntent,
    695                 String referrerPackageName,
    696                 int launchedFromUid) {
    697             super(context, pm, targetIntent, referrerPackageName, launchedFromUid);
    698         }
    699 
    700         @Override
    701         boolean isComponentPinned(ComponentName name) {
    702             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
    703         }
    704 
    705         @Override
    706         boolean isComponentFiltered(ComponentName name) {
    707             if (mFilteredComponentNames == null) {
    708                 return false;
    709             }
    710             for (ComponentName filteredComponentName : mFilteredComponentNames) {
    711                 if (name.equals(filteredComponentName)) {
    712                     return true;
    713                 }
    714             }
    715             return false;
    716         }
    717 
    718         @Override
    719         public float getScore(DisplayResolveInfo target) {
    720             if (target == null) {
    721                 return CALLER_TARGET_SCORE_BOOST;
    722             }
    723             float score = super.getScore(target);
    724             if (target.isPinned()) {
    725                 score += PINNED_TARGET_SCORE_BOOST;
    726             }
    727             return score;
    728         }
    729     }
    730 
    731     @Override
    732     public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    733             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
    734             boolean filterLastUsed) {
    735         final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
    736                 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
    737         return adapter;
    738     }
    739 
    740     @VisibleForTesting
    741     protected ResolverListController createListController() {
    742         return new ChooserListController(
    743                 this,
    744                 mPm,
    745                 getTargetIntent(),
    746                 getReferrerPackageName(),
    747                 mLaunchedFromUid);
    748     }
    749 
    750     final class ChooserTargetInfo implements TargetInfo {
    751         private final DisplayResolveInfo mSourceInfo;
    752         private final ResolveInfo mBackupResolveInfo;
    753         private final ChooserTarget mChooserTarget;
    754         private Drawable mBadgeIcon = null;
    755         private CharSequence mBadgeContentDescription;
    756         private Drawable mDisplayIcon;
    757         private final Intent mFillInIntent;
    758         private final int mFillInFlags;
    759         private final float mModifiedScore;
    760 
    761         public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
    762                 float modifiedScore) {
    763             mSourceInfo = sourceInfo;
    764             mChooserTarget = chooserTarget;
    765             mModifiedScore = modifiedScore;
    766             if (sourceInfo != null) {
    767                 final ResolveInfo ri = sourceInfo.getResolveInfo();
    768                 if (ri != null) {
    769                     final ActivityInfo ai = ri.activityInfo;
    770                     if (ai != null && ai.applicationInfo != null) {
    771                         final PackageManager pm = getPackageManager();
    772                         mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
    773                         mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
    774                     }
    775                 }
    776             }
    777             final Icon icon = chooserTarget.getIcon();
    778             // TODO do this in the background
    779             mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
    780 
    781             if (sourceInfo != null) {
    782                 mBackupResolveInfo = null;
    783             } else {
    784                 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
    785             }
    786 
    787             mFillInIntent = null;
    788             mFillInFlags = 0;
    789         }
    790 
    791         private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
    792             mSourceInfo = other.mSourceInfo;
    793             mBackupResolveInfo = other.mBackupResolveInfo;
    794             mChooserTarget = other.mChooserTarget;
    795             mBadgeIcon = other.mBadgeIcon;
    796             mBadgeContentDescription = other.mBadgeContentDescription;
    797             mDisplayIcon = other.mDisplayIcon;
    798             mFillInIntent = fillInIntent;
    799             mFillInFlags = flags;
    800             mModifiedScore = other.mModifiedScore;
    801         }
    802 
    803         public float getModifiedScore() {
    804             return mModifiedScore;
    805         }
    806 
    807         @Override
    808         public Intent getResolvedIntent() {
    809             if (mSourceInfo != null) {
    810                 return mSourceInfo.getResolvedIntent();
    811             }
    812 
    813             final Intent targetIntent = new Intent(getTargetIntent());
    814             targetIntent.setComponent(mChooserTarget.getComponentName());
    815             targetIntent.putExtras(mChooserTarget.getIntentExtras());
    816             return targetIntent;
    817         }
    818 
    819         @Override
    820         public ComponentName getResolvedComponentName() {
    821             if (mSourceInfo != null) {
    822                 return mSourceInfo.getResolvedComponentName();
    823             } else if (mBackupResolveInfo != null) {
    824                 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
    825                         mBackupResolveInfo.activityInfo.name);
    826             }
    827             return null;
    828         }
    829 
    830         private Intent getBaseIntentToSend() {
    831             Intent result = getResolvedIntent();
    832             if (result == null) {
    833                 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
    834             } else {
    835                 result = new Intent(result);
    836                 if (mFillInIntent != null) {
    837                     result.fillIn(mFillInIntent, mFillInFlags);
    838                 }
    839                 result.fillIn(mReferrerFillInIntent, 0);
    840             }
    841             return result;
    842         }
    843 
    844         @Override
    845         public boolean start(Activity activity, Bundle options) {
    846             throw new RuntimeException("ChooserTargets should be started as caller.");
    847         }
    848 
    849         @Override
    850         public boolean startAsCaller(Activity activity, Bundle options, int userId) {
    851             final Intent intent = getBaseIntentToSend();
    852             if (intent == null) {
    853                 return false;
    854             }
    855             intent.setComponent(mChooserTarget.getComponentName());
    856             intent.putExtras(mChooserTarget.getIntentExtras());
    857 
    858             // Important: we will ignore the target security checks in ActivityManager
    859             // if and only if the ChooserTarget's target package is the same package
    860             // where we got the ChooserTargetService that provided it. This lets a
    861             // ChooserTargetService provide a non-exported or permission-guarded target
    862             // to the chooser for the user to pick.
    863             //
    864             // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
    865             // so we'll obey the caller's normal security checks.
    866             final boolean ignoreTargetSecurity = mSourceInfo != null
    867                     && mSourceInfo.getResolvedComponentName().getPackageName()
    868                     .equals(mChooserTarget.getComponentName().getPackageName());
    869             activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
    870             return true;
    871         }
    872 
    873         @Override
    874         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
    875             throw new RuntimeException("ChooserTargets should be started as caller.");
    876         }
    877 
    878         @Override
    879         public ResolveInfo getResolveInfo() {
    880             return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
    881         }
    882 
    883         @Override
    884         public CharSequence getDisplayLabel() {
    885             return mChooserTarget.getTitle();
    886         }
    887 
    888         @Override
    889         public CharSequence getExtendedInfo() {
    890             // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
    891             return null;
    892         }
    893 
    894         @Override
    895         public Drawable getDisplayIcon() {
    896             return mDisplayIcon;
    897         }
    898 
    899         @Override
    900         public Drawable getBadgeIcon() {
    901             return mBadgeIcon;
    902         }
    903 
    904         @Override
    905         public CharSequence getBadgeContentDescription() {
    906             return mBadgeContentDescription;
    907         }
    908 
    909         @Override
    910         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
    911             return new ChooserTargetInfo(this, fillInIntent, flags);
    912         }
    913 
    914         @Override
    915         public List<Intent> getAllSourceIntents() {
    916             final List<Intent> results = new ArrayList<>();
    917             if (mSourceInfo != null) {
    918                 // We only queried the service for the first one in our sourceinfo.
    919                 results.add(mSourceInfo.getAllSourceIntents().get(0));
    920             }
    921             return results;
    922         }
    923 
    924         @Override
    925         public boolean isPinned() {
    926             return mSourceInfo != null ? mSourceInfo.isPinned() : false;
    927         }
    928     }
    929 
    930     public class ChooserListAdapter extends ResolveListAdapter {
    931         public static final int TARGET_BAD = -1;
    932         public static final int TARGET_CALLER = 0;
    933         public static final int TARGET_SERVICE = 1;
    934         public static final int TARGET_STANDARD = 2;
    935 
    936         private static final int MAX_SERVICE_TARGETS = 4;
    937         private static final int MAX_TARGETS_PER_SERVICE = 2;
    938 
    939         private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
    940         private final List<TargetInfo> mCallerTargets = new ArrayList<>();
    941         private boolean mShowServiceTargets;
    942 
    943         private float mLateFee = 1.f;
    944 
    945         private boolean mTargetsNeedPruning = false;
    946 
    947         private final BaseChooserTargetComparator mBaseTargetComparator
    948                 = new BaseChooserTargetComparator();
    949 
    950         public ChooserListAdapter(Context context, List<Intent> payloadIntents,
    951                 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
    952                 boolean filterLastUsed, ResolverListController resolverListController) {
    953             // Don't send the initial intents through the shared ResolverActivity path,
    954             // we want to separate them into a different section.
    955             super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
    956                     resolverListController);
    957 
    958             if (initialIntents != null) {
    959                 final PackageManager pm = getPackageManager();
    960                 for (int i = 0; i < initialIntents.length; i++) {
    961                     final Intent ii = initialIntents[i];
    962                     if (ii == null) {
    963                         continue;
    964                     }
    965 
    966                     // We reimplement Intent#resolveActivityInfo here because if we have an
    967                     // implicit intent, we want the ResolveInfo returned by PackageManager
    968                     // instead of one we reconstruct ourselves. The ResolveInfo returned might
    969                     // have extra metadata and resolvePackageName set and we want to respect that.
    970                     ResolveInfo ri = null;
    971                     ActivityInfo ai = null;
    972                     final ComponentName cn = ii.getComponent();
    973                     if (cn != null) {
    974                         try {
    975                             ai = pm.getActivityInfo(ii.getComponent(), 0);
    976                             ri = new ResolveInfo();
    977                             ri.activityInfo = ai;
    978                         } catch (PackageManager.NameNotFoundException ignored) {
    979                             // ai will == null below
    980                         }
    981                     }
    982                     if (ai == null) {
    983                         ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
    984                         ai = ri != null ? ri.activityInfo : null;
    985                     }
    986                     if (ai == null) {
    987                         Log.w(TAG, "No activity found for " + ii);
    988                         continue;
    989                     }
    990                     UserManager userManager =
    991                             (UserManager) getSystemService(Context.USER_SERVICE);
    992                     if (ii instanceof LabeledIntent) {
    993                         LabeledIntent li = (LabeledIntent)ii;
    994                         ri.resolvePackageName = li.getSourcePackage();
    995                         ri.labelRes = li.getLabelResource();
    996                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
    997                         ri.icon = li.getIconResource();
    998                         ri.iconResourceId = ri.icon;
    999                     }
   1000                     if (userManager.isManagedProfile()) {
   1001                         ri.noResourceId = true;
   1002                         ri.icon = 0;
   1003                     }
   1004                     mCallerTargets.add(new DisplayResolveInfo(ii, ri,
   1005                             ri.loadLabel(pm), null, ii));
   1006                 }
   1007             }
   1008         }
   1009 
   1010         @Override
   1011         public boolean showsExtendedInfo(TargetInfo info) {
   1012             // We have badges so we don't need this text shown.
   1013             return false;
   1014         }
   1015 
   1016         @Override
   1017         public boolean isComponentPinned(ComponentName name) {
   1018             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
   1019         }
   1020 
   1021         @Override
   1022         public View onCreateView(ViewGroup parent) {
   1023             return mInflater.inflate(
   1024                     com.android.internal.R.layout.resolve_grid_item, parent, false);
   1025         }
   1026 
   1027         @Override
   1028         public void onListRebuilt() {
   1029             // don't support direct share on low ram devices
   1030             if (ActivityManager.isLowRamDeviceStatic()) {
   1031                 return;
   1032             }
   1033 
   1034             if (mServiceTargets != null) {
   1035                 if (getDisplayInfoCount() == 0) {
   1036                     // b/109676071: When packages change, onListRebuilt() is called before
   1037                     // ResolverActivity.mDisplayList is re-populated; pruning now would cause the
   1038                     // list to disappear briefly, so instead we detect this case (the
   1039                     // set of targets suddenly dropping to zero) and remember to prune later.
   1040                     mTargetsNeedPruning = true;
   1041                 }
   1042             }
   1043             if (DEBUG) Log.d(TAG, "List built querying services");
   1044             queryTargetServices(this);
   1045         }
   1046 
   1047         @Override
   1048         public boolean shouldGetResolvedFilter() {
   1049             return true;
   1050         }
   1051 
   1052         @Override
   1053         public int getCount() {
   1054             return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
   1055         }
   1056 
   1057         @Override
   1058         public int getUnfilteredCount() {
   1059             return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
   1060         }
   1061 
   1062         public int getCallerTargetCount() {
   1063             return mCallerTargets.size();
   1064         }
   1065 
   1066         public int getServiceTargetCount() {
   1067             if (!mShowServiceTargets) {
   1068                 return 0;
   1069             }
   1070             return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
   1071         }
   1072 
   1073         public int getStandardTargetCount() {
   1074             return super.getCount();
   1075         }
   1076 
   1077         public int getPositionTargetType(int position) {
   1078             int offset = 0;
   1079 
   1080             final int callerTargetCount = getCallerTargetCount();
   1081             if (position < callerTargetCount) {
   1082                 return TARGET_CALLER;
   1083             }
   1084             offset += callerTargetCount;
   1085 
   1086             final int serviceTargetCount = getServiceTargetCount();
   1087             if (position - offset < serviceTargetCount) {
   1088                 return TARGET_SERVICE;
   1089             }
   1090             offset += serviceTargetCount;
   1091 
   1092             final int standardTargetCount = super.getCount();
   1093             if (position - offset < standardTargetCount) {
   1094                 return TARGET_STANDARD;
   1095             }
   1096 
   1097             return TARGET_BAD;
   1098         }
   1099 
   1100         @Override
   1101         public TargetInfo getItem(int position) {
   1102             return targetInfoForPosition(position, true);
   1103         }
   1104 
   1105         @Override
   1106         public TargetInfo targetInfoForPosition(int position, boolean filtered) {
   1107             int offset = 0;
   1108 
   1109             final int callerTargetCount = getCallerTargetCount();
   1110             if (position < callerTargetCount) {
   1111                 return mCallerTargets.get(position);
   1112             }
   1113             offset += callerTargetCount;
   1114 
   1115             final int serviceTargetCount = getServiceTargetCount();
   1116             if (position - offset < serviceTargetCount) {
   1117                 return mServiceTargets.get(position - offset);
   1118             }
   1119             offset += serviceTargetCount;
   1120 
   1121             return filtered ? super.getItem(position - offset)
   1122                     : getDisplayInfoAt(position - offset);
   1123         }
   1124 
   1125         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
   1126             if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
   1127                     + " targets");
   1128 
   1129             if (mTargetsNeedPruning && targets.size() > 0) {
   1130                 // First proper update since we got an onListRebuilt() with (transient) 0 items.
   1131                 // Clear out the target list and rebuild.
   1132                 mServiceTargets.clear();
   1133                 mTargetsNeedPruning = false;
   1134             }
   1135 
   1136             final float parentScore = getScore(origTarget);
   1137             Collections.sort(targets, mBaseTargetComparator);
   1138             float lastScore = 0;
   1139             for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) {
   1140                 final ChooserTarget target = targets.get(i);
   1141                 float targetScore = target.getScore();
   1142                 targetScore *= parentScore;
   1143                 targetScore *= mLateFee;
   1144                 if (i > 0 && targetScore >= lastScore) {
   1145                     // Apply a decay so that the top app can't crowd out everything else.
   1146                     // This incents ChooserTargetServices to define what's truly better.
   1147                     targetScore = lastScore * 0.95f;
   1148                 }
   1149                 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
   1150 
   1151                 if (DEBUG) {
   1152                     Log.d(TAG, " => " + target.toString() + " score=" + targetScore
   1153                             + " base=" + target.getScore()
   1154                             + " lastScore=" + lastScore
   1155                             + " parentScore=" + parentScore
   1156                             + " lateFee=" + mLateFee);
   1157                 }
   1158 
   1159                 lastScore = targetScore;
   1160             }
   1161 
   1162             mLateFee *= 0.95f;
   1163 
   1164             notifyDataSetChanged();
   1165         }
   1166 
   1167         /**
   1168          * Set to true to reveal all service targets at once.
   1169          */
   1170         public void setShowServiceTargets(boolean show) {
   1171             if (show != mShowServiceTargets) {
   1172                 mShowServiceTargets = show;
   1173                 notifyDataSetChanged();
   1174             }
   1175         }
   1176 
   1177         private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
   1178             final float newScore = chooserTargetInfo.getModifiedScore();
   1179             for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
   1180                 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
   1181                 if (newScore > serviceTarget.getModifiedScore()) {
   1182                     mServiceTargets.add(i, chooserTargetInfo);
   1183                     return;
   1184                 }
   1185             }
   1186             mServiceTargets.add(chooserTargetInfo);
   1187         }
   1188     }
   1189 
   1190     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
   1191         @Override
   1192         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
   1193             // Descending order
   1194             return (int) Math.signum(rhs.getScore() - lhs.getScore());
   1195         }
   1196     }
   1197 
   1198     class ChooserRowAdapter extends BaseAdapter {
   1199         private ChooserListAdapter mChooserListAdapter;
   1200         private final LayoutInflater mLayoutInflater;
   1201         private final int mColumnCount = 4;
   1202         private int mAnimationCount = 0;
   1203 
   1204         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
   1205             mChooserListAdapter = wrappedAdapter;
   1206             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
   1207 
   1208             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
   1209                 @Override
   1210                 public void onChanged() {
   1211                     super.onChanged();
   1212                     notifyDataSetChanged();
   1213                 }
   1214 
   1215                 @Override
   1216                 public void onInvalidated() {
   1217                     super.onInvalidated();
   1218                     notifyDataSetInvalidated();
   1219                 }
   1220             });
   1221         }
   1222 
   1223         @Override
   1224         public int getCount() {
   1225             return (int) (
   1226                     getCallerTargetRowCount()
   1227                     + getServiceTargetRowCount()
   1228                     + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
   1229             );
   1230         }
   1231 
   1232         public int getCallerTargetRowCount() {
   1233             return (int) Math.ceil(
   1234                     (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
   1235         }
   1236 
   1237         // There can be at most one row of service targets.
   1238         public int getServiceTargetRowCount() {
   1239             return (int) mChooserListAdapter.getServiceTargetCount() == 0 ? 0 : 1;
   1240         }
   1241 
   1242         @Override
   1243         public Object getItem(int position) {
   1244             // We have nothing useful to return here.
   1245             return position;
   1246         }
   1247 
   1248         @Override
   1249         public long getItemId(int position) {
   1250             return position;
   1251         }
   1252 
   1253         @Override
   1254         public View getView(int position, View convertView, ViewGroup parent) {
   1255             final RowViewHolder holder;
   1256             if (convertView == null) {
   1257                 holder = createViewHolder(parent);
   1258             } else {
   1259                 holder = (RowViewHolder) convertView.getTag();
   1260             }
   1261             bindViewHolder(position, holder);
   1262 
   1263             return holder.row;
   1264         }
   1265 
   1266         RowViewHolder createViewHolder(ViewGroup parent) {
   1267             final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
   1268                     parent, false);
   1269             final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
   1270             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1271 
   1272             for (int i = 0; i < mColumnCount; i++) {
   1273                 final View v = mChooserListAdapter.createView(row);
   1274                 final int column = i;
   1275                 v.setOnClickListener(new OnClickListener() {
   1276                     @Override
   1277                     public void onClick(View v) {
   1278                         startSelected(holder.itemIndices[column], false, true);
   1279                     }
   1280                 });
   1281                 v.setOnLongClickListener(new OnLongClickListener() {
   1282                     @Override
   1283                     public boolean onLongClick(View v) {
   1284                         showTargetDetails(
   1285                                 mChooserListAdapter.resolveInfoForPosition(
   1286                                         holder.itemIndices[column], true));
   1287                         return true;
   1288                     }
   1289                 });
   1290                 row.addView(v);
   1291                 holder.cells[i] = v;
   1292 
   1293                 // Force height to be a given so we don't have visual disruption during scaling.
   1294                 LayoutParams lp = v.getLayoutParams();
   1295                 v.measure(spec, spec);
   1296                 if (lp == null) {
   1297                     lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
   1298                     row.setLayoutParams(lp);
   1299                 } else {
   1300                     lp.height = v.getMeasuredHeight();
   1301                 }
   1302                 if (i != (mColumnCount - 1)) {
   1303                     row.addView(new Space(ChooserActivity.this),
   1304                             new LinearLayout.LayoutParams(0, 0, 1));
   1305                 }
   1306             }
   1307 
   1308             // Pre-measure so we can scale later.
   1309             holder.measure();
   1310             LayoutParams lp = row.getLayoutParams();
   1311             if (lp == null) {
   1312                 lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
   1313                 row.setLayoutParams(lp);
   1314             } else {
   1315                 lp.height = holder.measuredRowHeight;
   1316             }
   1317             row.setTag(holder);
   1318             return holder;
   1319         }
   1320 
   1321         void bindViewHolder(int rowPosition, RowViewHolder holder) {
   1322             final int start = getFirstRowPosition(rowPosition);
   1323             final int startType = mChooserListAdapter.getPositionTargetType(start);
   1324 
   1325             int end = start + mColumnCount - 1;
   1326             while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
   1327                 end--;
   1328             }
   1329 
   1330             if (startType == ChooserListAdapter.TARGET_SERVICE) {
   1331                 holder.row.setBackgroundColor(
   1332                         getColor(R.color.chooser_service_row_background_color));
   1333                 int nextStartType = mChooserListAdapter.getPositionTargetType(
   1334                         getFirstRowPosition(rowPosition + 1));
   1335                 int serviceSpacing = holder.row.getContext().getResources()
   1336                         .getDimensionPixelSize(R.dimen.chooser_service_spacing);
   1337                 if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
   1338                     // if the row is the only row for target service
   1339                     setVertPadding(holder, 0, 0);
   1340                 } else {
   1341                     int top = rowPosition == 0 ? serviceSpacing : 0;
   1342                     if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
   1343                         setVertPadding(holder, top, serviceSpacing);
   1344                     } else {
   1345                         setVertPadding(holder, top, 0);
   1346                     }
   1347                 }
   1348             } else {
   1349                 holder.row.setBackgroundColor(Color.TRANSPARENT);
   1350                 int lastStartType = mChooserListAdapter.getPositionTargetType(
   1351                         getFirstRowPosition(rowPosition - 1));
   1352                 if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
   1353                     int serviceSpacing = holder.row.getContext().getResources()
   1354                             .getDimensionPixelSize(R.dimen.chooser_service_spacing);
   1355                     setVertPadding(holder, serviceSpacing, 0);
   1356                 } else {
   1357                     setVertPadding(holder, 0, 0);
   1358                 }
   1359             }
   1360 
   1361             final int oldHeight = holder.row.getLayoutParams().height;
   1362             holder.row.getLayoutParams().height = Math.max(1, holder.measuredRowHeight);
   1363             if (holder.row.getLayoutParams().height != oldHeight) {
   1364                 holder.row.requestLayout();
   1365             }
   1366 
   1367             for (int i = 0; i < mColumnCount; i++) {
   1368                 final View v = holder.cells[i];
   1369                 if (start + i <= end) {
   1370                     v.setVisibility(View.VISIBLE);
   1371                     holder.itemIndices[i] = start + i;
   1372                     mChooserListAdapter.bindView(holder.itemIndices[i], v);
   1373                 } else {
   1374                     v.setVisibility(View.INVISIBLE);
   1375                 }
   1376             }
   1377         }
   1378 
   1379         private void setVertPadding(RowViewHolder holder, int top, int bottom) {
   1380             holder.row.setPadding(holder.row.getPaddingLeft(), top,
   1381                     holder.row.getPaddingRight(), bottom);
   1382         }
   1383 
   1384         int getFirstRowPosition(int row) {
   1385             final int callerCount = mChooserListAdapter.getCallerTargetCount();
   1386             final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
   1387 
   1388             if (row < callerRows) {
   1389                 return row * mColumnCount;
   1390             }
   1391 
   1392             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
   1393             final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
   1394 
   1395             if (row < callerRows + serviceRows) {
   1396                 return callerCount + (row - callerRows) * mColumnCount;
   1397             }
   1398 
   1399             return callerCount + serviceCount
   1400                     + (row - callerRows - serviceRows) * mColumnCount;
   1401         }
   1402     }
   1403 
   1404     static class RowViewHolder {
   1405         final View[] cells;
   1406         final ViewGroup row;
   1407         int measuredRowHeight;
   1408         int[] itemIndices;
   1409 
   1410         public RowViewHolder(ViewGroup row, int cellCount) {
   1411             this.row = row;
   1412             this.cells = new View[cellCount];
   1413             this.itemIndices = new int[cellCount];
   1414         }
   1415 
   1416         public void measure() {
   1417             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1418             row.measure(spec, spec);
   1419             measuredRowHeight = row.getMeasuredHeight();
   1420         }
   1421     }
   1422 
   1423     static class ChooserTargetServiceConnection implements ServiceConnection {
   1424         private DisplayResolveInfo mOriginalTarget;
   1425         private ComponentName mConnectedComponent;
   1426         private ChooserActivity mChooserActivity;
   1427         private final Object mLock = new Object();
   1428 
   1429         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
   1430             @Override
   1431             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
   1432                 synchronized (mLock) {
   1433                     if (mChooserActivity == null) {
   1434                         Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
   1435                                 + mConnectedComponent + "; ignoring...");
   1436                         return;
   1437                     }
   1438                     mChooserActivity.filterServiceTargets(
   1439                             mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
   1440                     final Message msg = Message.obtain();
   1441                     msg.what = CHOOSER_TARGET_SERVICE_RESULT;
   1442                     msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
   1443                             ChooserTargetServiceConnection.this);
   1444                     mChooserActivity.mChooserHandler.sendMessage(msg);
   1445                 }
   1446             }
   1447         };
   1448 
   1449         public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
   1450                 DisplayResolveInfo dri) {
   1451             mChooserActivity = chooserActivity;
   1452             mOriginalTarget = dri;
   1453         }
   1454 
   1455         @Override
   1456         public void onServiceConnected(ComponentName name, IBinder service) {
   1457             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
   1458             synchronized (mLock) {
   1459                 if (mChooserActivity == null) {
   1460                     Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
   1461                     return;
   1462                 }
   1463 
   1464                 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
   1465                 try {
   1466                     icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
   1467                             mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
   1468                 } catch (RemoteException e) {
   1469                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
   1470                     mChooserActivity.unbindService(this);
   1471                     mChooserActivity.mServiceConnections.remove(this);
   1472                     destroy();
   1473                 }
   1474             }
   1475         }
   1476 
   1477         @Override
   1478         public void onServiceDisconnected(ComponentName name) {
   1479             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
   1480             synchronized (mLock) {
   1481                 if (mChooserActivity == null) {
   1482                     Log.e(TAG,
   1483                             "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
   1484                     return;
   1485                 }
   1486 
   1487                 mChooserActivity.unbindService(this);
   1488                 mChooserActivity.mServiceConnections.remove(this);
   1489                 if (mChooserActivity.mServiceConnections.isEmpty()) {
   1490                     mChooserActivity.mChooserHandler.removeMessages(
   1491                             CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
   1492                     mChooserActivity.sendVoiceChoicesIfNeeded();
   1493                 }
   1494                 mConnectedComponent = null;
   1495                 destroy();
   1496             }
   1497         }
   1498 
   1499         public void destroy() {
   1500             synchronized (mLock) {
   1501                 mChooserActivity = null;
   1502                 mOriginalTarget = null;
   1503             }
   1504         }
   1505 
   1506         @Override
   1507         public String toString() {
   1508             return "ChooserTargetServiceConnection{service="
   1509                     + mConnectedComponent + ", activity="
   1510                     + (mOriginalTarget != null
   1511                     ? mOriginalTarget.getResolveInfo().activityInfo.toString()
   1512                     : "<connection destroyed>") + "}";
   1513         }
   1514     }
   1515 
   1516     static class ServiceResultInfo {
   1517         public final DisplayResolveInfo originalTarget;
   1518         public final List<ChooserTarget> resultTargets;
   1519         public final ChooserTargetServiceConnection connection;
   1520 
   1521         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
   1522                 ChooserTargetServiceConnection c) {
   1523             originalTarget = ot;
   1524             resultTargets = rt;
   1525             connection = c;
   1526         }
   1527     }
   1528 
   1529     static class RefinementResultReceiver extends ResultReceiver {
   1530         private ChooserActivity mChooserActivity;
   1531         private TargetInfo mSelectedTarget;
   1532 
   1533         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
   1534                 Handler handler) {
   1535             super(handler);
   1536             mChooserActivity = host;
   1537             mSelectedTarget = target;
   1538         }
   1539 
   1540         @Override
   1541         protected void onReceiveResult(int resultCode, Bundle resultData) {
   1542             if (mChooserActivity == null) {
   1543                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
   1544                 return;
   1545             }
   1546             if (resultData == null) {
   1547                 Log.e(TAG, "RefinementResultReceiver received null resultData");
   1548                 return;
   1549             }
   1550 
   1551             switch (resultCode) {
   1552                 case RESULT_CANCELED:
   1553                     mChooserActivity.onRefinementCanceled();
   1554                     break;
   1555                 case RESULT_OK:
   1556                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
   1557                     if (intentParcelable instanceof Intent) {
   1558                         mChooserActivity.onRefinementResult(mSelectedTarget,
   1559                                 (Intent) intentParcelable);
   1560                     } else {
   1561                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
   1562                                 + " in resultData with key Intent.EXTRA_INTENT");
   1563                     }
   1564                     break;
   1565                 default:
   1566                     Log.w(TAG, "Unknown result code " + resultCode
   1567                             + " sent to RefinementResultReceiver");
   1568                     break;
   1569             }
   1570         }
   1571 
   1572         public void destroy() {
   1573             mChooserActivity = null;
   1574             mSelectedTarget = null;
   1575         }
   1576     }
   1577 
   1578     class OffsetDataSetObserver extends DataSetObserver {
   1579         private final AbsListView mListView;
   1580         private int mCachedViewType = -1;
   1581         private View mCachedView;
   1582 
   1583         public OffsetDataSetObserver(AbsListView listView) {
   1584             mListView = listView;
   1585         }
   1586 
   1587         @Override
   1588         public void onChanged() {
   1589             if (mResolverDrawerLayout == null) {
   1590                 return;
   1591             }
   1592 
   1593             final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
   1594             int offset = 0;
   1595             for (int i = 0; i < chooserTargetRows; i++)  {
   1596                 final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
   1597                 final int vt = mChooserRowAdapter.getItemViewType(pos);
   1598                 if (vt != mCachedViewType) {
   1599                     mCachedView = null;
   1600                 }
   1601                 final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
   1602                 int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
   1603 
   1604                 offset += (int) (height);
   1605 
   1606                 if (vt >= 0) {
   1607                     mCachedViewType = vt;
   1608                     mCachedView = v;
   1609                 } else {
   1610                     mCachedViewType = -1;
   1611                 }
   1612             }
   1613 
   1614             mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
   1615         }
   1616     }
   1617 }
   1618