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