• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
makeMyIntent()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
onCreate(Bundle savedInstanceState)98     protected void onCreate(Bundle savedInstanceState) {
99         onCreate(savedInstanceState, makeMyIntent(),
100                 getText(R.string.whichApplication),
101                 null, null, true);
102     }
103 
onCreate(Bundle savedInstanceState, Intent intent, CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption)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
onRestart()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
onStop()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
onResume()190     protected void onResume() {
191         super.onResume();
192     }
193 
194     @Override
onPause()195     protected void onPause() {
196         super.onPause();
197     }
198 
199     @Override
getInitialState()200     protected Object getInitialState() {
201         return mInitialState;
202     }
203 
204     @Override
updateView()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
refreshActionList()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
setProperty(boolean enable)241     protected void setProperty(boolean enable) {
242     }
243 
getActions()244     private ArrayList<Action> getActions() {
245         ArrayList<Action> actions = new ArrayList<Action>();
246         return actions;
247     }
248 
249     @Override
onActionClicked(Action action)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 
startSelected(int which, boolean always)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 
onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck)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 
DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent)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 
ResolveListHelper(Context context, Intent intent, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid)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 
handlePackagesChanged()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 
rebuildList()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 
processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, CharSequence roLabel)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 
resolveInfoForPosition(int position)588         public ResolveInfo resolveInfoForPosition(int position) {
589             return mList.get(position).ri;
590         }
591 
intentForPosition(int position)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 
getCount()605         public int getCount() {
606             return mList.size();
607         }
608 
getActionForPosition(int position)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