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