George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 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 static android.app.prediction.AppTargetEvent.ACTION_LAUNCH; |
| 20 | |
| 21 | import android.app.prediction.AppPredictor; |
| 22 | import android.app.prediction.AppTarget; |
| 23 | import android.app.prediction.AppTargetEvent; |
| 24 | import android.app.prediction.AppTargetId; |
| 25 | import android.content.ComponentName; |
| 26 | import android.content.Context; |
| 27 | import android.content.Intent; |
| 28 | import android.content.pm.ResolveInfo; |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 29 | import android.os.Message; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 30 | import android.os.UserHandle; |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 31 | import android.provider.DeviceConfig; |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 32 | import android.util.Log; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 33 | |
| 34 | import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 35 | import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 36 | |
| 37 | import java.util.ArrayList; |
| 38 | import java.util.HashMap; |
| 39 | import java.util.List; |
| 40 | import java.util.Map; |
Song Hu | e2deffd | 2020-03-09 15:22:29 -0700 | [diff] [blame] | 41 | import java.util.Map.Entry; |
George Hodulik | ccb0cbc | 2019-05-02 12:23:44 -0700 | [diff] [blame] | 42 | import java.util.concurrent.Executors; |
Song Hu | e2deffd | 2020-03-09 15:22:29 -0700 | [diff] [blame] | 43 | import java.util.stream.Collectors; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 44 | |
| 45 | /** |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 46 | * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be |
| 47 | * disabled by returning an empty sorted target list, {@link AppPredictionServiceResolverComparator} |
| 48 | * will fallback to using a {@link ResolverRankerServiceResolverComparator}. |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 49 | */ |
| 50 | class AppPredictionServiceResolverComparator extends AbstractResolverComparator { |
| 51 | |
| 52 | private static final String TAG = "APSResolverComparator"; |
| 53 | |
| 54 | private final AppPredictor mAppPredictor; |
| 55 | private final Context mContext; |
| 56 | private final Map<ComponentName, Integer> mTargetRanks = new HashMap<>(); |
Song Hu | 6f42281 | 2020-04-26 21:51:31 -0700 | [diff] [blame] | 57 | private final Map<ComponentName, Integer> mTargetScores = new HashMap<>(); |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 58 | private final UserHandle mUser; |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 59 | private final Intent mIntent; |
| 60 | private final String mReferrerPackage; |
| 61 | // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall |
| 62 | // back to using the ResolverRankerService. |
| 63 | private ResolverRankerServiceResolverComparator mResolverRankerService; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 64 | |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 65 | private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( |
| 66 | DeviceConfig.NAMESPACE_SYSTEMUI, |
| 67 | SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, |
| 68 | true); |
| 69 | |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 70 | AppPredictionServiceResolverComparator( |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 71 | Context context, |
| 72 | Intent intent, |
| 73 | String referrerPackage, |
| 74 | AppPredictor appPredictor, |
| 75 | UserHandle user) { |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 76 | super(context, intent); |
| 77 | mContext = context; |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 78 | mIntent = intent; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 79 | mAppPredictor = appPredictor; |
| 80 | mUser = user; |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 81 | mReferrerPackage = referrerPackage; |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | @Override |
| 85 | int compare(ResolveInfo lhs, ResolveInfo rhs) { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 86 | if (mResolverRankerService != null) { |
| 87 | return mResolverRankerService.compare(lhs, rhs); |
| 88 | } |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 89 | Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName, |
| 90 | lhs.activityInfo.name)); |
| 91 | Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName, |
| 92 | rhs.activityInfo.name)); |
| 93 | if (lhsRank == null && rhsRank == null) { |
| 94 | return 0; |
| 95 | } else if (lhsRank == null) { |
| 96 | return -1; |
| 97 | } else if (rhsRank == null) { |
| 98 | return 1; |
| 99 | } |
| 100 | return lhsRank - rhsRank; |
| 101 | } |
| 102 | |
| 103 | @Override |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 104 | void doCompute(List<ResolvedComponentInfo> targets) { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 105 | if (targets.isEmpty()) { |
| 106 | mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT); |
| 107 | return; |
| 108 | } |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 109 | List<AppTarget> appTargets = new ArrayList<>(); |
| 110 | for (ResolvedComponentInfo target : targets) { |
George Hodulik | 9f572f4 | 2019-05-16 15:23:12 -0700 | [diff] [blame] | 111 | appTargets.add( |
| 112 | new AppTarget.Builder( |
| 113 | new AppTargetId(target.name.flattenToString()), |
| 114 | target.name.getPackageName(), |
| 115 | mUser) |
| 116 | .setClassName(target.name.getClassName()) |
| 117 | .build()); |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 118 | } |
George Hodulik | ccb0cbc | 2019-05-02 12:23:44 -0700 | [diff] [blame] | 119 | mAppPredictor.sortTargets(appTargets, Executors.newSingleThreadExecutor(), |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 120 | sortedAppTargets -> { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 121 | if (sortedAppTargets.isEmpty()) { |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 122 | Log.i(TAG, "AppPredictionService disabled. Using resolver."); |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 123 | // APS for chooser is disabled. Fallback to resolver. |
| 124 | mResolverRankerService = |
| 125 | new ResolverRankerServiceResolverComparator( |
| 126 | mContext, mIntent, mReferrerPackage, |
| 127 | () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT)); |
George Hodulik | ccb0cbc | 2019-05-02 12:23:44 -0700 | [diff] [blame] | 128 | mResolverRankerService.compute(targets); |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 129 | } else { |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 130 | Log.i(TAG, "AppPredictionService response received"); |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 131 | Message msg = |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 132 | Message.obtain(mHandler, RANKER_SERVICE_RESULT, sortedAppTargets); |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 133 | msg.sendToTarget(); |
| 134 | } |
| 135 | } |
| 136 | ); |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 137 | } |
| 138 | |
| 139 | @Override |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 140 | void handleResultMessage(Message msg) { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 141 | // Null value is okay if we have defaulted to the ResolverRankerService. |
| 142 | if (msg.what == RANKER_SERVICE_RESULT && msg.obj != null) { |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 143 | final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj; |
Song Hu | 6f42281 | 2020-04-26 21:51:31 -0700 | [diff] [blame] | 144 | if (checkAppTargetRankValid(sortedAppTargets)) { |
| 145 | sortedAppTargets.forEach(target -> mTargetScores.put( |
| 146 | new ComponentName(target.getPackageName(), target.getClassName()), |
| 147 | target.getRank())); |
| 148 | } |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 149 | for (int i = 0; i < sortedAppTargets.size(); i++) { |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 150 | ComponentName componentName = new ComponentName( |
| 151 | sortedAppTargets.get(i).getPackageName(), |
| 152 | sortedAppTargets.get(i).getClassName()); |
| 153 | mTargetRanks.put(componentName, i); |
| 154 | Log.i(TAG, "handleResultMessage, sortedAppTargets #" + i + ": " + componentName); |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 155 | } |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 156 | } else if (msg.obj == null && mResolverRankerService == null) { |
| 157 | Log.e(TAG, "Unexpected null result"); |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 158 | } |
| 159 | } |
| 160 | |
Song Hu | 6f42281 | 2020-04-26 21:51:31 -0700 | [diff] [blame] | 161 | private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) { |
| 162 | for (AppTarget target : sortedAppTargets) { |
| 163 | if (target.getRank() != 0) { |
| 164 | return true; |
| 165 | } |
| 166 | } |
| 167 | return false; |
| 168 | } |
| 169 | |
George Hodulik | 1f5d9bf | 2019-04-29 14:55:48 -0700 | [diff] [blame] | 170 | @Override |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 171 | float getScore(ComponentName name) { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 172 | if (mResolverRankerService != null) { |
| 173 | return mResolverRankerService.getScore(name); |
| 174 | } |
Song Hu | 5aab2d3 | 2020-04-30 10:07:15 -0700 | [diff] [blame] | 175 | if (mAppendDirectShareEnabled && !mTargetScores.isEmpty()) { |
Song Hu | 6f42281 | 2020-04-26 21:51:31 -0700 | [diff] [blame] | 176 | return mTargetScores.get(name); |
| 177 | } |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 178 | Integer rank = mTargetRanks.get(name); |
| 179 | if (rank == null) { |
| 180 | Log.w(TAG, "Score requested for unknown component."); |
| 181 | return 0f; |
| 182 | } |
| 183 | int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2; |
| 184 | return 1.0f - (((float) rank) / consecutiveSumOfRanks); |
| 185 | } |
| 186 | |
| 187 | @Override |
Song Hu | e2deffd | 2020-03-09 15:22:29 -0700 | [diff] [blame] | 188 | List<ComponentName> getTopComponentNames(int topK) { |
Song Hu | 36ba1b1 | 2020-04-14 15:36:17 -0700 | [diff] [blame] | 189 | if (mResolverRankerService != null) { |
| 190 | return mResolverRankerService.getTopComponentNames(topK); |
| 191 | } |
Song Hu | e2deffd | 2020-03-09 15:22:29 -0700 | [diff] [blame] | 192 | return mTargetRanks.entrySet().stream() |
| 193 | .sorted(Entry.comparingByValue()) |
| 194 | .limit(topK) |
| 195 | .map(Entry::getKey) |
| 196 | .collect(Collectors.toList()); |
| 197 | } |
| 198 | |
| 199 | @Override |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 200 | void updateModel(ComponentName componentName) { |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 201 | if (mResolverRankerService != null) { |
| 202 | mResolverRankerService.updateModel(componentName); |
| 203 | return; |
| 204 | } |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 205 | mAppPredictor.notifyAppTargetEvent( |
| 206 | new AppTargetEvent.Builder( |
| 207 | new AppTarget.Builder( |
| 208 | new AppTargetId(componentName.toString()), |
| 209 | componentName.getPackageName(), mUser) |
| 210 | .setClassName(componentName.getClassName()).build(), |
| 211 | ACTION_LAUNCH).build()); |
| 212 | } |
George Hodulik | 3f399f2 | 2019-04-26 16:17:54 -0700 | [diff] [blame] | 213 | |
| 214 | @Override |
| 215 | void destroy() { |
| 216 | if (mResolverRankerService != null) { |
| 217 | mResolverRankerService.destroy(); |
| 218 | mResolverRankerService = null; |
| 219 | } |
| 220 | } |
George Hodulik | c681ce4 | 2019-04-12 17:10:31 -0700 | [diff] [blame] | 221 | } |