• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.app.usage.UsageStatsManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.UserHandle;
29 import android.util.Log;
30 
31 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
32 
33 import java.util.ArrayList;
34 import java.util.Comparator;
35 import java.util.List;
36 
37 /**
38  * Used to sort resolved activities in {@link ResolverListController}.
39  */
40 abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
41 
42     private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
43     private static final boolean DEBUG = false;
44     private static final String TAG = "AbstractResolverComp";
45 
46     protected AfterCompute mAfterCompute;
47     protected final PackageManager mPm;
48     protected final UsageStatsManager mUsm;
49     protected String[] mAnnotations;
50     protected String mContentType;
51 
52     // True if the current share is a link.
53     private final boolean mHttp;
54     // can be null if mHttp == false or current user has no default browser package
55     private final String mDefaultBrowserPackageName;
56 
57     // message types
58     static final int RANKER_SERVICE_RESULT = 0;
59     static final int RANKER_RESULT_TIMEOUT = 1;
60 
61     // timeout for establishing connections with a ResolverRankerService, collecting features and
62     // predicting ranking scores.
63     private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
64 
65     protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
66         public void handleMessage(Message msg) {
67             switch (msg.what) {
68                 case RANKER_SERVICE_RESULT:
69                     if (DEBUG) {
70                         Log.d(TAG, "RANKER_SERVICE_RESULT");
71                     }
72                     if (mHandler.hasMessages(RANKER_RESULT_TIMEOUT)) {
73                         handleResultMessage(msg);
74                         mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
75                         afterCompute();
76                     }
77                     break;
78 
79                 case RANKER_RESULT_TIMEOUT:
80                     if (DEBUG) {
81                         Log.d(TAG, "RANKER_RESULT_TIMEOUT; unbinding services");
82                     }
83                     mHandler.removeMessages(RANKER_SERVICE_RESULT);
84                     afterCompute();
85                     break;
86 
87                 default:
88                     super.handleMessage(msg);
89             }
90         }
91     };
92 
AbstractResolverComparator(Context context, Intent intent)93     AbstractResolverComparator(Context context, Intent intent) {
94         String scheme = intent.getScheme();
95         mHttp = "http".equals(scheme) || "https".equals(scheme);
96         mContentType = intent.getType();
97         getContentAnnotations(intent);
98         mPm = context.getPackageManager();
99         mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
100         mDefaultBrowserPackageName = mHttp
101                 ? mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId())
102                 : null;
103     }
104 
105     // get annotations of content from intent.
getContentAnnotations(Intent intent)106     private void getContentAnnotations(Intent intent) {
107         ArrayList<String> annotations = intent.getStringArrayListExtra(
108                 Intent.EXTRA_CONTENT_ANNOTATIONS);
109         if (annotations != null) {
110             int size = annotations.size();
111             if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
112                 size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
113             }
114             mAnnotations = new String[size];
115             for (int i = 0; i < size; i++) {
116                 mAnnotations[i] = annotations.get(i);
117             }
118         }
119     }
120 
121     /**
122      * Callback to be called when {@link #compute(List)} finishes. This signals to stop waiting.
123      */
124     interface AfterCompute {
125 
afterCompute()126         void afterCompute();
127     }
128 
setCallBack(AfterCompute afterCompute)129     void setCallBack(AfterCompute afterCompute) {
130         mAfterCompute = afterCompute;
131     }
132 
afterCompute()133     protected final void afterCompute() {
134         final AfterCompute afterCompute = mAfterCompute;
135         if (afterCompute != null) {
136             afterCompute.afterCompute();
137         }
138     }
139 
140     @Override
compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp)141     public final int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
142         final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
143         final ResolveInfo rhs = rhsp.getResolveInfoAt(0);
144 
145         // We want to put the one targeted to another user at the end of the dialog.
146         if (lhs.targetUserId != UserHandle.USER_CURRENT) {
147             return rhs.targetUserId != UserHandle.USER_CURRENT ? 0 : 1;
148         }
149         if (rhs.targetUserId != UserHandle.USER_CURRENT) {
150             return -1;
151         }
152 
153         if (mHttp) {
154             // Special case: we want filters that match URI paths/schemes to be
155             // ordered before others.  This is for the case when opening URIs,
156             // to make native apps go above browsers - except for 1 even more special case
157             // which is the default browser, as we want that to go above them all.
158             if (isDefaultBrowser(lhs)) {
159                 return -1;
160             }
161 
162             if (isDefaultBrowser(rhs)) {
163                 return 1;
164             }
165             final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
166             final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
167             if (lhsSpecific != rhsSpecific) {
168                 return lhsSpecific ? -1 : 1;
169             }
170         }
171         return compare(lhs, rhs);
172     }
173 
174     /**
175      * Delegated to when used as a {@link Comparator<ResolvedComponentInfo>} if there is not a
176      * special case. The {@link ResolveInfo ResolveInfos} are the first {@link ResolveInfo} in
177      * {@link ResolvedComponentInfo#getResolveInfoAt(int)} from the parameters of {@link
178      * #compare(ResolvedComponentInfo, ResolvedComponentInfo)}
179      */
compare(ResolveInfo lhs, ResolveInfo rhs)180     abstract int compare(ResolveInfo lhs, ResolveInfo rhs);
181 
182     /**
183      * Computes features for each target. This will be called before calls to {@link
184      * #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
185      * comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
186      * ComponentName}, so the implementation will have to be prepared to identify a {@link
187      * ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
188      * before doing any computing.
189      */
compute(List<ResolvedComponentInfo> targets)190     final void compute(List<ResolvedComponentInfo> targets) {
191         beforeCompute();
192         doCompute(targets);
193     }
194 
195     /** Implementation of compute called after {@link #beforeCompute()}. */
doCompute(List<ResolvedComponentInfo> targets)196     abstract void doCompute(List<ResolvedComponentInfo> targets);
197 
198     /**
199      * Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
200      * when {@link #compute(List)} was called before this.
201      */
getScore(ComponentName name)202     abstract float getScore(ComponentName name);
203 
204     /** Handles result message sent to mHandler. */
handleResultMessage(Message message)205     abstract void handleResultMessage(Message message);
206 
207     /**
208      * Reports to UsageStats what was chosen.
209      */
updateChooserCounts(String packageName, int userId, String action)210     final void updateChooserCounts(String packageName, int userId, String action) {
211         if (mUsm != null) {
212             mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
213         }
214     }
215 
216     /**
217      * Updates the model used to rank the componentNames.
218      *
219      * <p>Default implementation does nothing, as we could have simple model that does not train
220      * online.
221      *
222      * @param componentName the component that the user clicked
223      */
updateModel(ComponentName componentName)224     void updateModel(ComponentName componentName) {
225     }
226 
227     /** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */
beforeCompute()228     void beforeCompute() {
229         if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + WATCHDOG_TIMEOUT_MILLIS + "ms");
230         if (mHandler == null) {
231             Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
232             return;
233         }
234         mHandler.sendEmptyMessageDelayed(RANKER_RESULT_TIMEOUT, WATCHDOG_TIMEOUT_MILLIS);
235     }
236 
237     /**
238      * Called when the {@link ResolverActivity} is destroyed. This calls {@link #afterCompute()}. If
239      * this call needs to happen at a different time during destroy, the method should be
240      * overridden.
241      */
destroy()242     void destroy() {
243         mHandler.removeMessages(RANKER_SERVICE_RESULT);
244         mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
245         afterCompute();
246     }
247 
isDefaultBrowser(ResolveInfo ri)248     private boolean isDefaultBrowser(ResolveInfo ri) {
249         // It makes sense to prefer the default browser
250         // only if the targeted user is the current user
251         if (ri.targetUserId != UserHandle.USER_CURRENT) {
252             return false;
253         }
254 
255         if (ri.activityInfo.packageName != null
256                     && ri.activityInfo.packageName.equals(mDefaultBrowserPackageName)) {
257             return true;
258         }
259         return false;
260     }
261 }
262