Home | History | Annotate | Download | only in resolver
      1 /*
      2  * Copyright (C) 2014 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.tv.settings.resolver;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.ActivityManagerNative;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.pm.ActivityInfo;
     26 import android.content.pm.LabeledIntent;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.graphics.drawable.Drawable;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.os.PatternMatcher;
     33 import android.os.RemoteException;
     34 import android.os.UserHandle;
     35 import android.util.Log;
     36 
     37 import com.android.internal.content.PackageMonitor;
     38 import com.android.tv.settings.R;
     39 import com.android.tv.settings.BaseSettingsActivity;
     40 import com.android.tv.settings.dialog.old.Action;
     41 import com.android.tv.settings.dialog.old.ActionAdapter;
     42 import com.android.tv.settings.dialog.old.ActionFragment;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Collections;
     46 import java.util.HashSet;
     47 import java.util.Iterator;
     48 import java.util.List;
     49 import java.util.Set;
     50 
     51 // NOTE: Aside from the Canvas specific UI part, a lot of the code in this activity has
     52 // been copied from the original ResolverActivity in frameworks:
     53 // com.android.internal.app.ResolverActivity.
     54 /**
     55  * Activity displaying remote information allowing remote settings to be set.
     56  */
     57 public class ResolverActivity extends BaseSettingsActivity implements ActionAdapter.Listener {
     58 
     59     private static final boolean DEBUG = false;
     60     private static final String TAG = "aah.ResolverActivity";
     61 
     62     private static final String KEY_BACK = "back";
     63     private static final String KEY_ALWAYS = "always";
     64     private static final String KEY_JUST_ONCE = "just_once";
     65 
     66     private static final String STATE_LIST = "list";
     67     private static final String STATE_NO_APPS = "no_apps";
     68     private static final String STATE_DO_ALWAYS = "always";
     69 
     70     private int mLaunchedFromUid;
     71     private String mTitle;
     72     private Object mInitialState;
     73     private ResolveListHelper mListHelper;
     74     private PackageManager mPm;
     75     private boolean mAlwaysUseOption;
     76     private int mSelectedIndex = -1;
     77 
     78     private boolean mRegistered;
     79     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
     80         @Override
     81         public void onSomePackagesChanged() {
     82             mListHelper.handlePackagesChanged();
     83         }
     84     };
     85 
     86     private Intent makeMyIntent() {
     87         Intent intent = new Intent(getIntent());
     88         // The resolver activity is set to be hidden from recent tasks.
     89         // we don't want this attribute to be propagated to the next activity
     90         // being launched. Note that if the original Intent also had this
     91         // flag set, we are now losing it. That should be a very rare case
     92         // and we can live with this.
     93         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
     94         return intent;
     95     }
     96 
     97     @Override
     98     protected void onCreate(Bundle savedInstanceState) {
     99         onCreate(savedInstanceState, makeMyIntent(),
    100                 getText(R.string.whichApplication),
    101                 null, null, true);
    102     }
    103 
    104     protected void onCreate(Bundle savedInstanceState, Intent intent,
    105             CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
    106             boolean alwaysUseOption) {
    107 
    108         if (DEBUG) {
    109             Log.d(TAG, "onCreate. Initial Intent: " + intent);
    110         }
    111         mLaunchedFromUid = 0;
    112         try {
    113             mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
    114                     getActivityToken());
    115         } catch (RemoteException e) {
    116             mLaunchedFromUid = -1;
    117         }
    118         mPm = getPackageManager();
    119         mAlwaysUseOption = alwaysUseOption;
    120         intent.setComponent(null);
    121 
    122         mPackageMonitor.register(this, getMainLooper(), false);
    123         mRegistered = true;
    124 
    125         mTitle = title.toString();
    126 
    127         mListHelper = new ResolveListHelper(this, intent, initialIntents, rList,
    128                 mLaunchedFromUid);
    129         int count = mListHelper.getCount();
    130 
    131         if (count > 0) {
    132             mInitialState = STATE_LIST;
    133         } else {
    134             mInitialState = STATE_NO_APPS;
    135         }
    136 
    137         super.onCreate(savedInstanceState);
    138 
    139         boolean finishNow = false;
    140 
    141         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
    142             finishNow = true;
    143         } else if (count == 1) {
    144             if (DEBUG) {
    145                 Log.d(TAG, "Starting Activity with Intent: " + mListHelper.intentForPosition(0));
    146             }
    147             startActivity(mListHelper.intentForPosition(0));
    148             finishNow = true;
    149         }
    150 
    151         if (finishNow) {
    152             mPackageMonitor.unregister();
    153             mRegistered = false;
    154             finish();
    155         }
    156     }
    157 
    158     @Override
    159     protected void onRestart() {
    160         super.onRestart();
    161         if (!mRegistered) {
    162             mPackageMonitor.register(this, getMainLooper(), false);
    163             mRegistered = true;
    164         }
    165         mListHelper.handlePackagesChanged();
    166     }
    167 
    168     @Override
    169     protected void onStop() {
    170         super.onStop();
    171         if (mRegistered) {
    172             mPackageMonitor.unregister();
    173             mRegistered = false;
    174         }
    175         if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
    176             // This resolver is in the unusual situation where it has been
    177             // launched at the top of a new task. We don't let it be added
    178             // to the recent tasks shown to the user, and we need to make sure
    179             // that each time we are launched we get the correct launching
    180             // uid (not re-using the same resolver from an old launching uid),
    181             // so we will now finish ourself since being no longer visible,
    182             // the user probably can't get back to us.
    183             if (!isChangingConfigurations()) {
    184                 finish();
    185             }
    186         }
    187     }
    188 
    189     @Override
    190     protected void onResume() {
    191         super.onResume();
    192     }
    193 
    194     @Override
    195     protected void onPause() {
    196         super.onPause();
    197     }
    198 
    199     @Override
    200     protected Object getInitialState() {
    201         return mInitialState;
    202     }
    203 
    204     @Override
    205     protected void updateView() {
    206         refreshActionList();
    207         if (mState == STATE_LIST) {
    208             setView(mTitle, null, null, 0);
    209         } else if (mState == STATE_NO_APPS) {
    210             setView(getString(R.string.noApplications), null, null, 0);
    211         } else if (mState == STATE_DO_ALWAYS) {
    212             setView(getString(R.string.alwaysUseQuestion), null, null, 0);
    213         }
    214     }
    215 
    216     @Override
    217     protected void refreshActionList() {
    218         mActions.clear();
    219         if (mState == STATE_LIST) {
    220             for (int i = 0; i < mListHelper.getCount(); i++) {
    221                 mActions.add(mListHelper.getActionForPosition(i));
    222             }
    223         } else if (mState == STATE_NO_APPS) {
    224             mActions.add(new Action.Builder()
    225                     .key(KEY_BACK)
    226                     .title(getString(R.string.noAppsGoBack))
    227                     .build());
    228         } else if (mState == STATE_DO_ALWAYS) {
    229             mActions.add(new Action.Builder().
    230                     key(KEY_ALWAYS)
    231                     .title(getString(R.string.alwaysUseOption))
    232                     .build());
    233             mActions.add(new Action.Builder()
    234                     .key(KEY_JUST_ONCE)
    235                     .title(getString(R.string.justOnceOption))
    236                     .build());
    237         }
    238     }
    239 
    240     @Override
    241     protected void setProperty(boolean enable) {
    242     }
    243 
    244     private ArrayList<Action> getActions() {
    245         ArrayList<Action> actions = new ArrayList<Action>();
    246         return actions;
    247     }
    248 
    249     @Override
    250     public void onActionClicked(Action action) {
    251         String key = action.getKey();
    252         if (key.compareTo(KEY_ALWAYS) == 0) {
    253             startSelected(mSelectedIndex, true);
    254         } else if (key.compareTo(KEY_JUST_ONCE) == 0) {
    255             startSelected(mSelectedIndex, false);
    256         } else if (key.compareTo(KEY_BACK) == 0) {
    257             finish();
    258         } else {
    259             int index = -1;
    260             try {
    261                 index = Integer.decode(key);
    262             } catch (NumberFormatException ex) {
    263             }
    264 
    265             if (index >= 0) {
    266                 if (mAlwaysUseOption) {
    267                     mSelectedIndex = index;
    268                     setState(STATE_DO_ALWAYS, true);
    269                 } else {
    270                     startSelected(index, false);
    271                 }
    272             } else {
    273                 finish();
    274             }
    275         }
    276     }
    277 
    278     void startSelected(int which, boolean always) {
    279         ResolveInfo ri = mListHelper.resolveInfoForPosition(which);
    280         Intent intent = mListHelper.intentForPosition(which);
    281         onIntentSelected(ri, intent, always);
    282         finish();
    283     }
    284 
    285     protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
    286         if (alwaysCheck) {
    287             // Build a reasonable intent filter, based on what matched.
    288             IntentFilter filter = new IntentFilter();
    289 
    290             if (intent.getAction() != null) {
    291                 filter.addAction(intent.getAction());
    292             }
    293             Set<String> categories = intent.getCategories();
    294             if (categories != null) {
    295                 for (String cat : categories) {
    296                     filter.addCategory(cat);
    297                 }
    298             }
    299             filter.addCategory(Intent.CATEGORY_DEFAULT);
    300 
    301             int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
    302             Uri data = intent.getData();
    303             if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
    304                 String mimeType = intent.resolveType(this);
    305                 if (mimeType != null) {
    306                     try {
    307                         filter.addDataType(mimeType);
    308                     } catch (IntentFilter.MalformedMimeTypeException e) {
    309                         Log.w("ResolverActivity", e);
    310                         filter = null;
    311                     }
    312                 }
    313             }
    314             if (data != null && data.getScheme() != null) {
    315                 // We need the data specification if there was no type,
    316                 // OR if the scheme is not one of our magical "file:"
    317                 // or "content:" schemes (see IntentFilter for the reason).
    318                 if (cat != IntentFilter.MATCH_CATEGORY_TYPE
    319                         || (!"file".equals(data.getScheme())
    320                                 && !"content".equals(data.getScheme()))) {
    321                     filter.addDataScheme(data.getScheme());
    322 
    323                     // Look through the resolved filter to determine which part
    324                     // of it matched the original Intent.
    325                     Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
    326                     if (pIt != null) {
    327                         String ssp = data.getSchemeSpecificPart();
    328                         while (ssp != null && pIt.hasNext()) {
    329                             PatternMatcher p = pIt.next();
    330                             if (p.match(ssp)) {
    331                                 filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
    332                                 break;
    333                             }
    334                         }
    335                     }
    336                     Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
    337                     if (aIt != null) {
    338                         while (aIt.hasNext()) {
    339                             IntentFilter.AuthorityEntry a = aIt.next();
    340                             if (a.match(data) >= 0) {
    341                                 int port = a.getPort();
    342                                 filter.addDataAuthority(a.getHost(),
    343                                         port >= 0 ? Integer.toString(port) : null);
    344                                 break;
    345                             }
    346                         }
    347                     }
    348                     pIt = ri.filter.pathsIterator();
    349                     if (pIt != null) {
    350                         String path = data.getPath();
    351                         while (path != null && pIt.hasNext()) {
    352                             PatternMatcher p = pIt.next();
    353                             if (p.match(path)) {
    354                                 filter.addDataPath(p.getPath(), p.getType());
    355                                 break;
    356                             }
    357                         }
    358                     }
    359                 }
    360             }
    361 
    362             if (filter != null) {
    363                 final int N = mListHelper.mList.size();
    364                 ComponentName[] set = new ComponentName[N];
    365                 int bestMatch = 0;
    366                 for (int i = 0; i < N; i++) {
    367                     ResolveInfo r = mListHelper.mList.get(i).ri;
    368                     set[i] = new ComponentName(r.activityInfo.packageName,
    369                             r.activityInfo.name);
    370                     if (r.match > bestMatch)
    371                         bestMatch = r.match;
    372                 }
    373                 getPackageManager().addPreferredActivity(filter, bestMatch, set,
    374                         intent.getComponent());
    375             }
    376         }
    377 
    378         if (intent != null) {
    379             startActivity(intent);
    380         }
    381     }
    382 
    383     private final class DisplayResolveInfo {
    384         ResolveInfo ri;
    385         CharSequence displayLabel;
    386         Drawable displayIcon;
    387         CharSequence extendedInfo;
    388         Intent origIntent;
    389 
    390         DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel,
    391                 CharSequence pInfo, Intent pOrigIntent) {
    392             ri = pri;
    393             displayLabel = pLabel;
    394             extendedInfo = pInfo;
    395             origIntent = pOrigIntent;
    396         }
    397     }
    398 
    399     private final class ResolveListHelper {
    400         private final Intent[] mInitialIntents;
    401         private final List<ResolveInfo> mBaseResolveList;
    402         private final Intent mIntent;
    403         private final int mLaunchedFromUid;
    404 
    405         private List<DisplayResolveInfo> mList;
    406 
    407         public ResolveListHelper(Context context, Intent intent,
    408                 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
    409             mIntent = new Intent(intent);
    410             mIntent.setComponent(null);
    411             mInitialIntents = initialIntents;
    412             mBaseResolveList = rList;
    413             mLaunchedFromUid = launchedFromUid;
    414             mList = new ArrayList<DisplayResolveInfo>();
    415             rebuildList();
    416         }
    417 
    418         public void handlePackagesChanged() {
    419             final int oldItemCount = getCount();
    420             rebuildList();
    421             refreshActionList();
    422             ActionAdapter adapter = (ActionAdapter) ((ActionFragment) mActionFragment).getAdapter();
    423             if (adapter != null) {
    424                 adapter.setActions(mActions);
    425             }
    426             final int newItemCount = getCount();
    427             if (newItemCount == 0) {
    428                 // We no longer have any items... just finish the activity.
    429                 finish();
    430             }
    431         }
    432 
    433         private void rebuildList() {
    434             List<ResolveInfo> currentResolveList;
    435 
    436             mList.clear();
    437             if (mBaseResolveList != null) {
    438                 currentResolveList = mBaseResolveList;
    439             } else {
    440                 currentResolveList = mPm.queryIntentActivities(
    441                         mIntent, PackageManager.MATCH_DEFAULT_ONLY
    442                                 | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
    443                 // Filter out any activities that the launched uid does not
    444                 // have permission for. We don't do this when we have an explicit
    445                 // list of resolved activities, because that only happens when
    446                 // we are being subclassed, so we can safely launch whatever
    447                 // they gave us.
    448                 if (currentResolveList != null) {
    449                     for (int i = currentResolveList.size() - 1; i >= 0; i--) {
    450                         ActivityInfo ai = currentResolveList.get(i).activityInfo;
    451                         int granted = ActivityManager.checkComponentPermission(
    452                                 ai.permission, mLaunchedFromUid,
    453                                 ai.applicationInfo.uid, ai.exported);
    454                         if (granted != PackageManager.PERMISSION_GRANTED) {
    455                             // Access not allowed!
    456                             currentResolveList.remove(i);
    457                         }
    458                     }
    459                 }
    460             }
    461             int N;
    462             if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
    463                 // Only display the first matches that are either of equal
    464                 // priority or have asked to be default options.
    465                 ResolveInfo r0 = currentResolveList.get(0);
    466                 for (int i = 1; i < N; i++) {
    467                     ResolveInfo ri = currentResolveList.get(i);
    468                     if (DEBUG)
    469                         Log.v("ResolveListActivity",
    470                                 r0.activityInfo.name + "=" +
    471                                 r0.priority + "/" + r0.isDefault + " vs " +
    472                                 ri.activityInfo.name + "=" +
    473                                 ri.priority + "/" + ri.isDefault);
    474                     if (r0.priority != ri.priority ||
    475                             r0.isDefault != ri.isDefault) {
    476                         while (i < N) {
    477                             currentResolveList.remove(i);
    478                             N--;
    479                         }
    480                     }
    481                 }
    482                 if (N > 1) {
    483                     ResolveInfo.DisplayNameComparator rComparator =
    484                             new ResolveInfo.DisplayNameComparator(mPm);
    485                     Collections.sort(currentResolveList, rComparator);
    486                 }
    487                 // First put the initial items at the top.
    488                 if (mInitialIntents != null) {
    489                     for (int i = 0; i < mInitialIntents.length; i++) {
    490                         Intent ii = mInitialIntents[i];
    491                         if (ii == null) {
    492                             continue;
    493                         }
    494                         ActivityInfo ai = ii.resolveActivityInfo(
    495                                 getPackageManager(), 0);
    496                         if (ai == null) {
    497                             Log.w("ResolverActivity", "No activity found for "
    498                                     + ii);
    499                             continue;
    500                         }
    501                         ResolveInfo ri = new ResolveInfo();
    502                         ri.activityInfo = ai;
    503                         if (ii instanceof LabeledIntent) {
    504                             LabeledIntent li = (LabeledIntent) ii;
    505                             ri.resolvePackageName = li.getSourcePackage();
    506                             ri.labelRes = li.getLabelResource();
    507                             ri.nonLocalizedLabel = li.getNonLocalizedLabel();
    508                             ri.icon = li.getIconResource();
    509                         }
    510                         mList.add(new DisplayResolveInfo(ri,
    511                                 ri.loadLabel(getPackageManager()), null, ii));
    512                     }
    513                 }
    514 
    515                 // Check for applications with same name and use application
    516                 // name or package name if necessary
    517                 r0 = currentResolveList.get(0);
    518                 int start = 0;
    519                 CharSequence r0Label = r0.loadLabel(mPm);
    520                 for (int i = 1; i < N; i++) {
    521                     if (r0Label == null) {
    522                         r0Label = r0.activityInfo.packageName;
    523                     }
    524                     ResolveInfo ri = currentResolveList.get(i);
    525                     CharSequence riLabel = ri.loadLabel(mPm);
    526                     if (riLabel == null) {
    527                         riLabel = ri.activityInfo.packageName;
    528                     }
    529                     if (riLabel.equals(r0Label)) {
    530                         continue;
    531                     }
    532                     processGroup(currentResolveList, start, (i - 1), r0, r0Label);
    533                     r0 = ri;
    534                     r0Label = riLabel;
    535                     start = i;
    536                 }
    537                 // Process last group
    538                 processGroup(currentResolveList, start, (N - 1), r0, r0Label);
    539             }
    540         }
    541 
    542         private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
    543                 CharSequence roLabel) {
    544             // Process labels from start to i
    545             int num = end - start + 1;
    546             if (num == 1) {
    547                 // No duplicate labels. Use label for entry at start
    548                 mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
    549             } else {
    550                 boolean usePkg = false;
    551                 CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
    552                 if (startApp == null) {
    553                     usePkg = true;
    554                 }
    555                 if (!usePkg) {
    556                     // Use HashSet to track duplicates
    557                     HashSet<CharSequence> duplicates =
    558                             new HashSet<CharSequence>();
    559                     duplicates.add(startApp);
    560                     for (int j = start + 1; j <= end; j++) {
    561                         ResolveInfo jRi = rList.get(j);
    562                         CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
    563                         if ((jApp == null) || (duplicates.contains(jApp))) {
    564                             usePkg = true;
    565                             break;
    566                         } else {
    567                             duplicates.add(jApp);
    568                         }
    569                     }
    570                     // Clear HashSet for later use
    571                     duplicates.clear();
    572                 }
    573                 for (int k = start; k <= end; k++) {
    574                     ResolveInfo add = rList.get(k);
    575                     if (usePkg) {
    576                         // Use package name for all entries from start to end-1
    577                         mList.add(new DisplayResolveInfo(add, roLabel,
    578                                 add.activityInfo.packageName, null));
    579                     } else {
    580                         // Use application name for all entries from start to end-1
    581                         mList.add(new DisplayResolveInfo(add, roLabel,
    582                                 add.activityInfo.applicationInfo.loadLabel(mPm), null));
    583                     }
    584                 }
    585             }
    586         }
    587 
    588         public ResolveInfo resolveInfoForPosition(int position) {
    589             return mList.get(position).ri;
    590         }
    591 
    592         public Intent intentForPosition(int position) {
    593             DisplayResolveInfo dri = mList.get(position);
    594 
    595             Intent intent = new Intent(dri.origIntent != null
    596                     ? dri.origIntent : mIntent);
    597             intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
    598                     | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
    599             ActivityInfo ai = dri.ri.activityInfo;
    600             intent.setComponent(new ComponentName(
    601                     ai.applicationInfo.packageName, ai.name));
    602             return intent;
    603         }
    604 
    605         public int getCount() {
    606             return mList.size();
    607         }
    608 
    609         public Action getActionForPosition(int position) {
    610             DisplayResolveInfo info = mList.get(position);
    611 
    612             String resPkgName = null;
    613             int iconId = 0;
    614 
    615             if (info.ri.resolvePackageName != null && info.ri.icon != 0) {
    616                 resPkgName = info.ri.resolvePackageName;
    617                 iconId = info.ri.icon;
    618             } else if (info.ri.activityInfo.packageName != null && info.ri.getIconResource() != 0) {
    619                 resPkgName = info.ri.activityInfo.packageName;
    620                 iconId = info.ri.getIconResource();
    621             }
    622 
    623             Action act = new Action.Builder()
    624                     .key(Integer.toString(position))
    625                     .title(info.displayLabel.toString())
    626                     .description(info.extendedInfo != null ? info.extendedInfo.toString() : null)
    627                     .resourcePackageName(resPkgName)
    628                     .drawableResource(iconId)
    629                     .build();
    630 
    631             return act;
    632         }
    633     }
    634 }
    635