diff options
9 files changed, 435 insertions, 205 deletions
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java index eb08e03c14de..eaa0781f12ef 100644 --- a/services/people/java/com/android/server/people/SessionInfo.java +++ b/services/people/java/com/android/server/people/SessionInfo.java @@ -25,7 +25,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.people.data.DataManager; -import com.android.server.people.prediction.ConversationPredictor; +import com.android.server.people.prediction.AppTargetPredictor; import java.util.List; @@ -34,12 +34,12 @@ class SessionInfo { private static final String TAG = "SessionInfo"; - private final ConversationPredictor mConversationPredictor; + private final AppTargetPredictor mAppTargetPredictor; private final RemoteCallbackList<IPredictionCallback> mCallbacks = new RemoteCallbackList<>(); SessionInfo(AppPredictionContext predictionContext, DataManager dataManager) { - mConversationPredictor = new ConversationPredictor(predictionContext, + mAppTargetPredictor = AppTargetPredictor.create(predictionContext, this::updatePredictions, dataManager); } @@ -51,8 +51,8 @@ class SessionInfo { mCallbacks.unregister(callback); } - ConversationPredictor getPredictor() { - return mConversationPredictor; + AppTargetPredictor getPredictor() { + return mAppTargetPredictor; } void onDestroy() { diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 13cce414ea7c..8a2a0c77a4dc 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -59,7 +59,6 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.telephony.SmsApplication; import com.android.server.LocalServices; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; @@ -199,6 +198,13 @@ public class DataManager { } } + /** Gets the {@link PackageData} for the given package and user. */ + @Nullable + public PackageData getPackage(@NonNull String packageName, @UserIdInt int userId) { + UserData userData = getUnlockedUserData(userId); + return userData != null ? userData.getPackageData(packageName) : null; + } + /** Gets the {@link ShortcutInfo} for the given shortcut ID. */ @Nullable public ShortcutInfo getShortcut(@NonNull String packageName, @UserIdInt int userId, @@ -212,20 +218,11 @@ public class DataManager { } /** - * Gets the conversation {@link ShareShortcutInfo}s from all packages owned by the calling user - * that match the specified {@link IntentFilter}. + * Gets the {@link ShareShortcutInfo}s from all packages owned by the calling user that match + * the specified {@link IntentFilter}. */ - public List<ShareShortcutInfo> getConversationShareTargets( - @NonNull IntentFilter intentFilter) { - List<ShareShortcutInfo> shareShortcuts = mShortcutManager.getShareTargets(intentFilter); - List<ShareShortcutInfo> result = new ArrayList<>(); - for (ShareShortcutInfo shareShortcut : shareShortcuts) { - ShortcutInfo si = shareShortcut.getShortcutInfo(); - if (getConversationInfo(si.getPackage(), si.getUserId(), si.getId()) != null) { - result.add(shareShortcut); - } - } - return result; + public List<ShareShortcutInfo> getShareShortcuts(@NonNull IntentFilter intentFilter) { + return mShortcutManager.getShareTargets(intentFilter); } /** Reports the {@link AppTargetEvent} from App Prediction Manager. */ @@ -236,7 +233,7 @@ public class DataManager { if (shortcutInfo == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) { return; } - PackageData packageData = getPackageData(appTarget.getPackageName(), + PackageData packageData = getPackage(appTarget.getPackageName(), appTarget.getUser().getIdentifier()); if (packageData == null) { return; @@ -283,20 +280,6 @@ public class DataManager { return userData != null && userData.isUnlocked() ? userData : null; } - @Nullable - private PackageData getPackageData(@NonNull String packageName, int userId) { - UserData userData = getUnlockedUserData(userId); - return userData != null ? userData.getPackageData(packageName) : null; - } - - @Nullable - private ConversationInfo getConversationInfo(@NonNull String packageName, @UserIdInt int userId, - @NonNull String shortcutId) { - PackageData packageData = getPackageData(packageName, userId); - return packageData != null ? packageData.getConversationStore().getConversation(shortcutId) - : null; - } - private void updateDefaultDialer(@NonNull UserData userData) { TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); String defaultDialer = telecomManager != null @@ -318,7 +301,7 @@ public class DataManager { if (shortcutId == null) { return null; } - PackageData packageData = getPackageData(sbn.getPackageName(), + PackageData packageData = getPackage(sbn.getPackageName(), sbn.getUser().getIdentifier()); if (packageData == null || packageData.getConversationStore().getConversation(shortcutId) == null) { @@ -382,7 +365,7 @@ public class DataManager { usageEvents.getNextEvent(e); String packageName = e.getPackageName(); - PackageData packageData = getPackageData(packageName, userId); + PackageData packageData = getPackage(packageName, userId); if (packageData == null) { continue; } diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index 9c22a7f1c484..35b65ecb0ebc 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -17,6 +17,7 @@ package com.android.server.people.data; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; @@ -68,6 +69,15 @@ public class PackageData { } /** + * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link + * ConversationInfo} does not exist. + */ + @Nullable + public ConversationInfo getConversationInfo(@NonNull String shortcutId) { + return getConversationStore().getConversation(shortcutId); + } + + /** * Gets the combined {@link EventHistory} for a given shortcut ID. This returned {@link * EventHistory} has events of all types, no matter whether they're annotated with shortcut ID, * Locus ID, or phone number etc. diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java new file mode 100644 index 000000000000..44f3e35833d9 --- /dev/null +++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.prediction; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.WorkerThread; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.people.data.DataManager; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +/** + * Predictor that predicts the {@link AppTarget} the user is most likely to open. + */ +public class AppTargetPredictor { + + private static final String UI_SURFACE_SHARE = "share"; + + /** Creates a {@link AppTargetPredictor} instance based on the prediction context. */ + public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext, + @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, + @NonNull DataManager dataManager) { + if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) { + return new ShareTargetPredictor( + predictionContext, updatePredictionsMethod, dataManager); + } + return new AppTargetPredictor(predictionContext, updatePredictionsMethod, dataManager); + } + + private final AppPredictionContext mPredictionContext; + private final Consumer<List<AppTarget>> mUpdatePredictionsMethod; + private final DataManager mDataManager; + private final ExecutorService mCallbackExecutor; + + AppTargetPredictor(@NonNull AppPredictionContext predictionContext, + @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, + @NonNull DataManager dataManager) { + mPredictionContext = predictionContext; + mUpdatePredictionsMethod = updatePredictionsMethod; + mDataManager = dataManager; + mCallbackExecutor = Executors.newSingleThreadExecutor(); + } + + /** + * Called by the client app to indicate a target launch. + */ + @MainThread + public void onAppTargetEvent(AppTargetEvent event) { + } + + /** + * Called by the client app to indicate a particular location has been shown to the user. + */ + @MainThread + public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) { + } + + /** + * Called by the client app to request sorting of the provided targets based on the prediction + * ranking. + */ + @MainThread + public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { + mCallbackExecutor.execute(() -> sortTargets(targets, callback)); + } + + /** + * Called by the client app to request target predictions. + */ + @MainThread + public void onRequestPredictionUpdate() { + mCallbackExecutor.execute(this::predictTargets); + } + + @VisibleForTesting + public Consumer<List<AppTarget>> getUpdatePredictionsMethod() { + return mUpdatePredictionsMethod; + } + + /** To be overridden by the subclass to predict the targets. */ + @WorkerThread + void predictTargets() { + } + + /** + * To be overridden by the subclass to sort the provided targets based on the prediction + * ranking. + */ + @WorkerThread + void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { + callback.accept(targets); + } + + AppPredictionContext getPredictionContext() { + return mPredictionContext; + } + + DataManager getDataManager() { + return mDataManager; + } + + void updatePredictions(List<AppTarget> targets) { + mUpdatePredictionsMethod.accept(targets); + } +} diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java deleted file mode 100644 index ed8a56bb6435..000000000000 --- a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.people.prediction; - -import android.annotation.MainThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.prediction.AppPredictionContext; -import android.app.prediction.AppTarget; -import android.app.prediction.AppTargetEvent; -import android.app.prediction.AppTargetId; -import android.content.IntentFilter; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager.ShareShortcutInfo; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.ChooserActivity; -import com.android.server.people.data.DataManager; -import com.android.server.people.data.EventHistory; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; - -/** - * Predictor that predicts the conversations or apps the user is most likely to open. - */ -public class ConversationPredictor { - - private static final String UI_SURFACE_SHARE = "share"; - - private final AppPredictionContext mPredictionContext; - private final Consumer<List<AppTarget>> mUpdatePredictionsMethod; - private final DataManager mDataManager; - private final ExecutorService mCallbackExecutor; - @Nullable - private final IntentFilter mIntentFilter; - - public ConversationPredictor(@NonNull AppPredictionContext predictionContext, - @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, - @NonNull DataManager dataManager) { - mPredictionContext = predictionContext; - mUpdatePredictionsMethod = updatePredictionsMethod; - mDataManager = dataManager; - mCallbackExecutor = Executors.newSingleThreadExecutor(); - if (UI_SURFACE_SHARE.equals(mPredictionContext.getUiSurface())) { - mIntentFilter = mPredictionContext.getExtras().getParcelable( - ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY); - } else { - mIntentFilter = null; - } - } - - /** - * Called by the client app to indicate a target launch. - */ - @MainThread - public void onAppTargetEvent(AppTargetEvent event) { - mDataManager.reportAppTargetEvent(event, mIntentFilter); - } - - /** - * Called by the client app to indicate a particular location has been shown to the user. - */ - @MainThread - public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {} - - /** - * Called by the client app to request sorting of the provided targets based on the prediction - * ranking. - */ - @MainThread - public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { - mCallbackExecutor.execute(() -> callback.accept(targets)); - } - - /** - * Called by the client app to request target predictions. - */ - @MainThread - public void onRequestPredictionUpdate() { - // TODO: Re-route the call to different ranking classes for different surfaces. - mCallbackExecutor.execute(() -> { - List<AppTarget> targets = new ArrayList<>(); - if (mIntentFilter != null) { - List<ShareShortcutInfo> shareShortcuts = - mDataManager.getConversationShareTargets(mIntentFilter); - for (ShareShortcutInfo shareShortcut : shareShortcuts) { - ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); - AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId()); - String shareTargetClass = shareShortcut.getTargetComponent().getClassName(); - targets.add(new AppTarget.Builder(appTargetId, shortcutInfo) - .setClassName(shareTargetClass) - .build()); - } - } else { - List<ConversationData> conversationDataList = new ArrayList<>(); - mDataManager.forAllPackages(packageData -> - packageData.forAllConversations(conversationInfo -> { - EventHistory eventHistory = packageData.getEventHistory( - conversationInfo.getShortcutId()); - ConversationData conversationData = new ConversationData( - packageData.getPackageName(), packageData.getUserId(), - conversationInfo, eventHistory); - conversationDataList.add(conversationData); - })); - for (ConversationData conversationData : conversationDataList) { - String shortcutId = conversationData.getConversationInfo().getShortcutId(); - ShortcutInfo shortcut = mDataManager.getShortcut( - conversationData.getPackageName(), conversationData.getUserId(), - shortcutId); - if (shortcut != null) { - AppTargetId appTargetId = new AppTargetId(shortcut.getId()); - targets.add(new AppTarget.Builder(appTargetId, shortcut).build()); - } - } - } - mUpdatePredictionsMethod.accept(targets); - }); - } - - @VisibleForTesting - public Consumer<List<AppTarget>> getUpdatePredictionsMethod() { - return mUpdatePredictionsMethod; - } -} diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java new file mode 100644 index 000000000000..280ced3a07c5 --- /dev/null +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.prediction; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.IntentFilter; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager.ShareShortcutInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ChooserActivity; +import com.android.server.people.data.ConversationInfo; +import com.android.server.people.data.DataManager; +import com.android.server.people.data.EventHistory; +import com.android.server.people.data.PackageData; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +class ShareTargetPredictor extends AppTargetPredictor { + + private final IntentFilter mIntentFilter; + + ShareTargetPredictor(@NonNull AppPredictionContext predictionContext, + @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, + @NonNull DataManager dataManager) { + super(predictionContext, updatePredictionsMethod, dataManager); + mIntentFilter = predictionContext.getExtras().getParcelable( + ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY); + } + + @MainThread + @Override + public void onAppTargetEvent(AppTargetEvent event) { + getDataManager().reportAppTargetEvent(event, mIntentFilter); + } + + @WorkerThread + @Override + protected void predictTargets() { + List<ShareTarget> shareTargets = getShareTargets(); + // TODO: Rank the share targets with the data in ShareTarget.mConversationData. + List<AppTarget> appTargets = new ArrayList<>(); + for (ShareTarget shareTarget : shareTargets) { + + ShortcutInfo shortcutInfo = shareTarget.getShareShortcutInfo().getShortcutInfo(); + AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId()); + String shareTargetClassName = + shareTarget.getShareShortcutInfo().getTargetComponent().getClassName(); + AppTarget appTarget = new AppTarget.Builder(appTargetId, shortcutInfo) + .setClassName(shareTargetClassName) + .build(); + appTargets.add(appTarget); + if (appTargets.size() >= getPredictionContext().getPredictedTargetCount()) { + break; + } + } + updatePredictions(appTargets); + } + + @VisibleForTesting + List<ShareTarget> getShareTargets() { + List<ShareTarget> shareTargets = new ArrayList<>(); + List<ShareShortcutInfo> shareShortcuts = + getDataManager().getShareShortcuts(mIntentFilter); + + for (ShareShortcutInfo shareShortcut : shareShortcuts) { + ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); + String packageName = shortcutInfo.getPackage(); + int userId = shortcutInfo.getUserId(); + PackageData packageData = getDataManager().getPackage(packageName, userId); + + ConversationData conversationData = null; + if (packageData != null) { + String shortcutId = shortcutInfo.getId(); + ConversationInfo conversationInfo = + packageData.getConversationInfo(shortcutId); + + if (conversationInfo != null) { + EventHistory eventHistory = packageData.getEventHistory(shortcutId); + conversationData = new ConversationData( + packageName, userId, conversationInfo, eventHistory); + } + } + shareTargets.add(new ShareTarget(shareShortcut, conversationData)); + } + + return shareTargets; + } + + @VisibleForTesting + static class ShareTarget { + + @NonNull + private final ShareShortcutInfo mShareShortcutInfo; + @Nullable + private final ConversationData mConversationData; + + private ShareTarget(@NonNull ShareShortcutInfo shareShortcutInfo, + @Nullable ConversationData conversationData) { + mShareShortcutInfo = shareShortcutInfo; + mConversationData = conversationData; + } + + @NonNull + @VisibleForTesting + ShareShortcutInfo getShareShortcutInfo() { + return mShareShortcutInfo; + } + + @Nullable + @VisibleForTesting + ConversationData getConversationData() { + return mConversationData; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java index d3166b91dc9e..4ae374abb7c2 100644 --- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java @@ -28,6 +28,7 @@ import android.app.prediction.IPredictionCallback; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteException; import com.android.server.LocalServices; @@ -76,6 +77,7 @@ public final class PeopleServiceTest { mPredictionContext = new AppPredictionContext.Builder(mContext) .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT) + .setExtras(new Bundle()) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 9f3d656188e1..4008cdc596dc 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -40,14 +40,12 @@ import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; -import android.content.pm.ShortcutManager.ShareShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.database.ContentObserver; @@ -224,31 +222,6 @@ public final class DataManagerTest { } @Test - public void testGetShareTargets() { - mDataManager.onUserUnlocked(USER_ID_PRIMARY); - - ShortcutInfo shortcut1 = - buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson()); - ShareShortcutInfo shareShortcut1 = - new ShareShortcutInfo(shortcut1, new ComponentName("pkg_1", "activity")); - - ShortcutInfo shortcut2 = - buildShortcutInfo("pkg_2", USER_ID_PRIMARY, "sc_2", buildPerson()); - ShareShortcutInfo shareShortcut2 = - new ShareShortcutInfo(shortcut2, new ComponentName("pkg_2", "activity")); - mDataManager.onShortcutAddedOrUpdated(shortcut2); - - when(mShortcutManager.getShareTargets(any(IntentFilter.class))) - .thenReturn(Arrays.asList(shareShortcut1, shareShortcut2)); - - List<ShareShortcutInfo> shareShortcuts = - mDataManager.getConversationShareTargets(new IntentFilter()); - // Only "sc_2" is stored as a conversation. - assertEquals(1, shareShortcuts.size()); - assertEquals("sc_2", shareShortcuts.get(0).getShortcutInfo().getId()); - } - - @Test public void testReportAppTargetEvent() throws IntentFilter.MalformedMimeTypeException { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java new file mode 100644 index 000000000000..808906e3a06a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.prediction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.prediction.AppPredictionContext; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager.ShareShortcutInfo; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.server.people.data.ConversationInfo; +import com.android.server.people.data.DataManager; +import com.android.server.people.data.EventHistory; +import com.android.server.people.data.PackageData; +import com.android.server.people.prediction.ShareTargetPredictor.ShareTarget; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(JUnit4.class) +public final class ShareTargetPredictorTest { + + private static final String UI_SURFACE_SHARE = "share"; + private static final int NUM_PREDICTED_TARGETS = 5; + private static final int USER_ID = 0; + private static final String PACKAGE_1 = "pkg1"; + private static final String CLASS_1 = "cls1"; + private static final String PACKAGE_2 = "pkg2"; + private static final String CLASS_2 = "cls2"; + + @Mock private Context mContext; + @Mock private DataManager mDataManager; + @Mock private PackageData mPackageData1; + @Mock private PackageData mPackageData2; + + private List<ShareShortcutInfo> mShareShortcuts = new ArrayList<>(); + + private ShareTargetPredictor mPredictor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mDataManager.getShareShortcuts(any())).thenReturn(mShareShortcuts); + when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1); + when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2); + + AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext) + .setUiSurface(UI_SURFACE_SHARE) + .setPredictedTargetCount(NUM_PREDICTED_TARGETS) + .setExtras(new Bundle()) + .build(); + mPredictor = new ShareTargetPredictor(predictionContext, targets -> { }, mDataManager); + } + + @Test + public void testGetShareTargets() { + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); + + when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class)); + // "sc4" does not have a ConversationInfo. + + when(mPackageData1.getEventHistory(anyString())).thenReturn(mock(EventHistory.class)); + when(mPackageData2.getEventHistory(anyString())).thenReturn(mock(EventHistory.class)); + + List<ShareTarget> shareTargets = mPredictor.getShareTargets(); + + assertEquals(4, shareTargets.size()); + + assertEquals("sc1", shareTargets.get(0).getShareShortcutInfo().getShortcutInfo().getId()); + assertNotNull(shareTargets.get(0).getConversationData()); + + assertEquals("sc2", shareTargets.get(1).getShareShortcutInfo().getShortcutInfo().getId()); + assertNotNull(shareTargets.get(1).getConversationData()); + + assertEquals("sc3", shareTargets.get(2).getShareShortcutInfo().getShortcutInfo().getId()); + assertNotNull(shareTargets.get(2).getConversationData()); + + assertEquals("sc4", shareTargets.get(3).getShareShortcutInfo().getShortcutInfo().getId()); + assertNull(shareTargets.get(3).getConversationData()); + } + + private ShareShortcutInfo buildShareShortcut( + String packageName, String className, String shortcutId) { + ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId); + ComponentName componentName = new ComponentName(packageName, className); + return new ShareShortcutInfo(shortcutInfo, componentName); + } + + private ShortcutInfo buildShortcut(String packageName, String shortcutId) { + Context mockContext = mock(Context.class); + when(mockContext.getPackageName()).thenReturn(packageName); + when(mockContext.getUserId()).thenReturn(USER_ID); + when(mockContext.getUser()).thenReturn(UserHandle.of(USER_ID)); + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, shortcutId) + .setShortLabel(shortcutId) + .setIntent(new Intent("TestIntent")); + return builder.build(); + } +} |