1 /* 2 * Copyright (C) 2020 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.server.people.prediction; 18 19 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; 20 21 import static java.util.Collections.reverseOrder; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.UserIdInt; 26 import android.annotation.WorkerThread; 27 import android.app.prediction.AppPredictionContext; 28 import android.app.prediction.AppPredictionManager; 29 import android.app.prediction.AppPredictor; 30 import android.app.prediction.AppTarget; 31 import android.app.prediction.AppTargetEvent; 32 import android.app.prediction.AppTargetId; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.IntentFilter; 36 import android.content.pm.ShortcutInfo; 37 import android.content.pm.ShortcutManager.ShareShortcutInfo; 38 import android.os.UserHandle; 39 import android.provider.DeviceConfig; 40 import android.util.Log; 41 import android.util.Slog; 42 43 import com.android.internal.R; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.app.ChooserActivity; 46 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 47 import com.android.server.people.data.ConversationInfo; 48 import com.android.server.people.data.DataManager; 49 import com.android.server.people.data.EventHistory; 50 import com.android.server.people.data.PackageData; 51 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.Comparator; 55 import java.util.List; 56 import java.util.function.Consumer; 57 58 /** 59 * Predictor that predicts the {@link AppTarget} the user is most likely to open on share sheet. 60 */ 61 class ShareTargetPredictor extends AppTargetPredictor { 62 63 private static final String TAG = "ShareTargetPredictor"; 64 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 65 private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor"; 66 private final IntentFilter mIntentFilter; 67 private final AppPredictor mRemoteAppPredictor; 68 @Nullable private final String mChooserActivity; 69 ShareTargetPredictor(@onNull AppPredictionContext predictionContext, @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, @NonNull DataManager dataManager, @UserIdInt int callingUserId, @NonNull Context context)70 ShareTargetPredictor(@NonNull AppPredictionContext predictionContext, 71 @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, 72 @NonNull DataManager dataManager, 73 @UserIdInt int callingUserId, @NonNull Context context) { 74 super(predictionContext, updatePredictionsMethod, dataManager, callingUserId); 75 mIntentFilter = predictionContext.getExtras().getParcelable( 76 ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, android.content.IntentFilter.class); 77 if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, 78 SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED, 79 false)) { 80 predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true); 81 mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0) 82 .getSystemService(AppPredictionManager.class) 83 .createAppPredictionSession(predictionContext); 84 } else { 85 mRemoteAppPredictor = null; 86 } 87 ComponentName component = ComponentName.unflattenFromString( 88 context.getResources().getString(R.string.config_chooserActivity)); 89 mChooserActivity = (component == null) ? null : component.getShortClassName(); 90 } 91 92 /** Reports chosen history of direct/app share targets. */ 93 @WorkerThread 94 @Override reportAppTargetEvent(AppTargetEvent event)95 void reportAppTargetEvent(AppTargetEvent event) { 96 if (DEBUG) { 97 Slog.d(TAG, "reportAppTargetEvent"); 98 } 99 if (mIntentFilter != null) { 100 getDataManager().reportShareTargetEvent(event, mIntentFilter); 101 } 102 if (mRemoteAppPredictor != null) { 103 mRemoteAppPredictor.notifyAppTargetEvent(event); 104 } 105 } 106 107 /** Provides prediction on direct share targets */ 108 @WorkerThread 109 @Override predictTargets()110 void predictTargets() { 111 if (DEBUG) { 112 Slog.d(TAG, "predictTargets"); 113 } 114 if (mIntentFilter == null) { 115 updatePredictions(List.of()); 116 return; 117 } 118 List<ShareTarget> shareTargets = getDirectShareTargets(); 119 SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter), 120 System.currentTimeMillis()); 121 Collections.sort(shareTargets, 122 Comparator.comparing(ShareTarget::getScore, reverseOrder()) 123 .thenComparing(t -> t.getAppTarget().getRank())); 124 List<AppTarget> res = new ArrayList<>(); 125 for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(), 126 shareTargets.size()); i++) { 127 res.add(shareTargets.get(i).getAppTarget()); 128 } 129 updatePredictions(res); 130 } 131 132 /** Provides prediction on app share targets */ 133 @WorkerThread 134 @Override sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback)135 void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { 136 if (DEBUG) { 137 Slog.d(TAG, "sortTargets"); 138 } 139 if (mIntentFilter == null) { 140 callback.accept(targets); 141 return; 142 } 143 List<ShareTarget> shareTargets = getAppShareTargets(targets); 144 SharesheetModelScorer.computeScoreForAppShare(shareTargets, 145 getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(), 146 System.currentTimeMillis(), getDataManager(), 147 mCallingUserId, mChooserActivity); 148 Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore())); 149 List<AppTarget> appTargetList = new ArrayList<>(); 150 for (ShareTarget shareTarget : shareTargets) { 151 AppTarget appTarget = shareTarget.getAppTarget(); 152 appTargetList.add(new AppTarget.Builder(appTarget.getId(), appTarget.getPackageName(), 153 appTarget.getUser()) 154 .setClassName(appTarget.getClassName()) 155 .setRank(shareTarget.getScore() > 0 ? (int) (shareTarget.getScore() 156 * 1000) : 0) 157 .build()); 158 } 159 callback.accept(appTargetList); 160 } 161 162 /** Recycles resources. */ 163 @WorkerThread 164 @Override destroy()165 void destroy() { 166 if (mRemoteAppPredictor != null) { 167 mRemoteAppPredictor.destroy(); 168 } 169 } 170 getDirectShareTargets()171 private List<ShareTarget> getDirectShareTargets() { 172 List<ShareTarget> shareTargets = new ArrayList<>(); 173 List<ShareShortcutInfo> shareShortcuts = 174 getDataManager().getShareShortcuts(mIntentFilter, mCallingUserId); 175 176 for (ShareShortcutInfo shareShortcut : shareShortcuts) { 177 ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); 178 AppTarget appTarget = new AppTarget.Builder( 179 new AppTargetId(shortcutInfo.getId()), 180 shortcutInfo) 181 .setClassName(shareShortcut.getTargetComponent().getClassName()) 182 .setRank(shortcutInfo.getRank()) 183 .build(); 184 String packageName = shortcutInfo.getPackage(); 185 int userId = shortcutInfo.getUserId(); 186 PackageData packageData = getDataManager().getPackage(packageName, userId); 187 188 ConversationInfo conversationInfo = null; 189 EventHistory eventHistory = null; 190 if (packageData != null) { 191 String shortcutId = shortcutInfo.getId(); 192 conversationInfo = packageData.getConversationInfo(shortcutId); 193 if (conversationInfo != null) { 194 eventHistory = packageData.getEventHistory(shortcutId); 195 } 196 } 197 shareTargets.add(new ShareTarget(appTarget, eventHistory, conversationInfo)); 198 } 199 200 return shareTargets; 201 } 202 getAppShareTargets(List<AppTarget> targets)203 private List<ShareTarget> getAppShareTargets(List<AppTarget> targets) { 204 List<ShareTarget> shareTargets = new ArrayList<>(); 205 for (AppTarget target : targets) { 206 PackageData packageData = getDataManager().getPackage(target.getPackageName(), 207 target.getUser().getIdentifier()); 208 shareTargets.add(new ShareTarget(target, 209 packageData == null ? null 210 : packageData.getClassLevelEventHistory(target.getClassName()), null)); 211 } 212 return shareTargets; 213 } 214 getShareEventType(IntentFilter intentFilter)215 private int getShareEventType(IntentFilter intentFilter) { 216 String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null; 217 return getDataManager().mimeTypeToShareEventType(mimeType); 218 } 219 220 @VisibleForTesting 221 static class ShareTarget { 222 223 @NonNull 224 private final AppTarget mAppTarget; 225 @Nullable 226 private final EventHistory mEventHistory; 227 @Nullable 228 private final ConversationInfo mConversationInfo; 229 private float mScore; 230 231 @VisibleForTesting ShareTarget(@onNull AppTarget appTarget, @Nullable EventHistory eventHistory, @Nullable ConversationInfo conversationInfo)232 ShareTarget(@NonNull AppTarget appTarget, 233 @Nullable EventHistory eventHistory, 234 @Nullable ConversationInfo conversationInfo) { 235 mAppTarget = appTarget; 236 mEventHistory = eventHistory; 237 mConversationInfo = conversationInfo; 238 mScore = 0f; 239 } 240 241 @NonNull 242 @VisibleForTesting getAppTarget()243 AppTarget getAppTarget() { 244 return mAppTarget; 245 } 246 247 @Nullable 248 @VisibleForTesting getEventHistory()249 EventHistory getEventHistory() { 250 return mEventHistory; 251 } 252 253 @Nullable 254 @VisibleForTesting getConversationInfo()255 ConversationInfo getConversationInfo() { 256 return mConversationInfo; 257 } 258 259 @VisibleForTesting getScore()260 float getScore() { 261 return mScore; 262 } 263 264 @VisibleForTesting setScore(float score)265 void setScore(float score) { 266 mScore = score; 267 } 268 } 269 } 270