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