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