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