Merge "Moving all-apps predictions to Launcher model" into ub-launcher3-master

This commit is contained in:
TreeHugger Robot 2020-07-29 22:02:59 +00:00 committed by Android (Google) Code Review
commit 89b670056f
20 changed files with 424 additions and 805 deletions

View File

@ -25,8 +25,11 @@
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
<string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>
<string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
</resources>

View File

@ -30,7 +30,6 @@ import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.IntProperty;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Interpolator;
@ -44,19 +43,15 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@ -67,15 +62,12 @@ import com.android.launcher3.util.Themes;
import com.android.quickstep.AnimatedFloat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@TargetApi(Build.VERSION_CODES.P)
public class PredictionRowView extends LinearLayout implements
LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
private static final String TAG = "PredictionRowView";
private static final IntProperty<PredictionRowView> TEXT_ALPHA =
new IntProperty<PredictionRowView>("textAlpha") {
@Override
@ -93,16 +85,14 @@ public class PredictionRowView extends LinearLayout implements
(t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
private final Launcher mLauncher;
private final PredictionUiStateManager mPredictionUiStateManager;
private int mNumPredictedAppsPerRow;
// The set of predicted app component names
private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
private final ArrayList<ItemInfoWithIcon> mPredictedApps = new ArrayList<>();
// Helper to drawing the focus indicator.
private final FocusIndicatorHelper mFocusHelper;
// The set of predicted apps resolved from the component names and the current set of apps
private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
private final int mIconTextColor;
private final int mIconFullTextAlpha;
private int mIconLastSetTextAlpha;
@ -134,8 +124,6 @@ public class PredictionRowView extends LinearLayout implements
mLauncher = Launcher.getLauncher(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context);
mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
mIconFullTextAlpha = Color.alpha(mIconTextColor);
mIconCurrentTextAlpha = mIconFullTextAlpha;
@ -146,24 +134,9 @@ public class PredictionRowView extends LinearLayout implements
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView());
getAppsStore().registerIconContainer(this);
AllAppsTipView.scheduleShowIfNeeded(mLauncher);
}
private AllAppsStore getAppsStore() {
return mLauncher.getAppsView().getAppsStore();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPredictionUiStateManager.setTargetAppsView(null);
getAppsStore().unregisterIconContainer(this);
}
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
mParent = parent;
}
@ -205,7 +178,7 @@ public class PredictionRowView extends LinearLayout implements
* Returns the predicted apps.
*/
public List<ItemInfoWithIcon> getPredictedApps() {
return mPredictedApps;
return new ArrayList<>(mPredictedApps);
}
/**
@ -217,12 +190,12 @@ public class PredictionRowView extends LinearLayout implements
* If the number of predicted apps is the same as the previous list of predicted apps,
* we can optimize by swapping them in place.
*/
public void setPredictedApps(List<ComponentKeyMapper> apps) {
mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps);
public void setPredictedApps(List<ItemInfo> items) {
mPredictedApps.clear();
mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
items.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
.map(itemInfo -> (WorkspaceItemInfo) itemInfo)
.forEach(mPredictedApps::add);
applyPredictionApps();
}
@ -264,11 +237,7 @@ public class PredictionRowView extends LinearLayout implements
icon.reset();
if (predictionCount > i) {
icon.setVisibility(View.VISIBLE);
if (mPredictedApps.get(i) instanceof AppInfo) {
icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i));
} else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) {
icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i));
}
icon.applyFromWorkspaceItem(mPredictedApps.get(i));
icon.setTextColor(iconColor);
} else {
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
@ -284,33 +253,6 @@ public class PredictionRowView extends LinearLayout implements
mParent.onHeightUpdated();
}
private List<ItemInfoWithIcon> processPredictedAppComponents(
List<ComponentKeyMapper> components) {
if (getAppsStore().getApps().length == 0) {
// Apps have not been bound yet.
return Collections.emptyList();
}
List<ItemInfoWithIcon> predictedApps = new ArrayList<>();
for (ComponentKeyMapper mapper : components) {
ItemInfoWithIcon info = mapper.getApp(getAppsStore());
if (info != null) {
ItemInfoWithIcon predictedApp = info.clone();
predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
predictedApps.add(predictedApp);
} else {
if (FeatureFlags.IS_STUDIO_BUILD) {
Log.e(TAG, "Predicted app not found: " + mapper);
}
}
// Stop at the number of predicted apps
if (predictedApps.size() == mNumPredictedAppsPerRow) {
break;
}
}
return predictedApps;
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> parents) {

View File

@ -1,348 +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.launcher3.appprediction;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
/**
* Handler responsible to updating the UI due to predicted apps changes. Operations:
* 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes
* invisible again before applying the changes. This ensures that the UI does not change abruptly
* in front of the user, even if an app launched and user pressed back button to return to the
* all-apps UI again.
* 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory
* even if all-apps is not opened as they are shown in search UI as well
* 3) Load instant app if it is not already in memory. As predictions are persisted on disk,
* instant app will not be in memory when launcher starts.
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
* that client id.
*/
public class PredictionUiStateManager implements StateListener<LauncherState>,
ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
// TODO (b/129421797): Update the client constants
public enum Client {
HOME("home");
public final String id;
Client(String id) {
this.id = id;
}
}
public static final MainThreadInitializedObject<PredictionUiStateManager> INSTANCE =
new MainThreadInitializedObject<>(PredictionUiStateManager::new);
private final Context mContext;
private final DynamicItemCache mDynamicItemCache;
private List mPredictionServicePredictions = Collections.emptyList();
private int mMaxIconsPerRow;
private AllAppsContainerView mAppsView;
private PredictionState mPendingState;
private PredictionState mCurrentState;
private boolean mGettingValidPredictionResults;
private PredictionUiStateManager(Context context) {
mContext = context;
mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
mMaxIconsPerRow = idp.numColumns;
idp.addOnChangeListener(this);
mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
.getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
// Call this last
mCurrentState = parseLastState();
}
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
mMaxIconsPerRow = profile.numColumns;
}
public void setTargetAppsView(AllAppsContainerView appsView) {
if (mAppsView != null) {
mAppsView.getAppsStore().removeUpdateListener(this);
}
mAppsView = appsView;
if (mAppsView != null) {
mAppsView.getAppsStore().addUpdateListener(this);
}
if (mPendingState != null) {
applyState(mPendingState);
mPendingState = null;
} else {
applyState(mCurrentState);
}
updateDependencies(mCurrentState);
}
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) { }
@Override
public void onStateTransitionComplete(LauncherState state) {
if (mAppsView == null) {
return;
}
if (mPendingState != null && canApplyPredictions(mPendingState)) {
applyState(mPendingState);
mPendingState = null;
}
if (mPendingState == null) {
Launcher.getLauncher(mAppsView.getContext()).getStateManager()
.removeStateListener(this);
}
}
private void scheduleApplyPredictedApps(PredictionState state) {
boolean registerListener = mPendingState == null;
mPendingState = state;
if (registerListener) {
// Add a listener and wait until appsView is invisible again.
Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
}
}
private void applyState(PredictionState state) {
mCurrentState = state;
if (mAppsView != null) {
mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mCurrentState.apps);
}
}
private void updatePredictionStateAfterCallback() {
boolean validResults = mPredictionServicePredictions != null
&& !mPredictionServicePredictions.isEmpty();
if (validResults != mGettingValidPredictionResults) {
mGettingValidPredictionResults = validResults;
Utilities.getDevicePrefs(mContext).edit()
.putBoolean(LAST_PREDICTION_ENABLED_STATE, true)
.apply();
}
dispatchOnChange(true);
}
public AppPredictor.Callback appPredictorCallback(Client client) {
return targets -> {
mPredictionServicePredictions = targets;
updatePredictionStateAfterCallback();
};
}
private void dispatchOnChange(boolean changed) {
PredictionState newState = changed
? parseLastState()
: mPendingState != null && canApplyPredictions(mPendingState)
? mPendingState
: mCurrentState;
if (changed && mAppsView != null && !canApplyPredictions(newState)) {
scheduleApplyPredictedApps(newState);
} else {
applyState(newState);
}
}
private PredictionState parseLastState() {
PredictionState state = new PredictionState();
state.isEnabled = mGettingValidPredictionResults;
if (!state.isEnabled) {
state.apps = Collections.EMPTY_LIST;
return state;
}
state.apps = new ArrayList<>();
List<AppTarget> appTargets = mPredictionServicePredictions;
if (!appTargets.isEmpty()) {
for (AppTarget appTarget : appTargets) {
ComponentKey key;
if (appTarget.getShortcutInfo() != null) {
key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
} else {
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
}
updateDependencies(state);
return state;
}
private void updateDependencies(PredictionState state) {
if (!state.isEnabled || mAppsView == null) {
return;
}
mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
mMaxIconsPerRow);
}
@Override
public void onAppsUpdated() {
dispatchOnChange(false);
}
private boolean canApplyPredictions(PredictionState newState) {
if (mAppsView == null) {
// If there is no apps view, no need to schedule.
return true;
}
Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
PredictionRowView predictionRow = mAppsView.getFloatingHeaderView().
findFixedRowByType(PredictionRowView.class);
if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 ||
launcher.isForceInvisible()) {
return true;
}
if (mCurrentState.isEnabled != newState.isEnabled
|| mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) {
// If the visibility of the prediction row is changing, apply immediately.
return true;
}
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
// If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way
return false;
}
if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) {
// Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI
return false;
}
// Instead of checking against 1, we should check against (1 + delta), where delta accounts
// for the nav-bar height (as app icon can still be visible under the nav-bar). Checking
// against 1, keeps the logic simple :)
return launcher.getAllAppsController().getProgress() > 1;
}
public PredictionState getCurrentState() {
return mCurrentState;
}
/**
* Returns ranking info for the app within all apps prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
* {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
* {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
* {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
*/
public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) {
if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) {
return OptionalInt.empty();
}
if (itemInfo.itemType == ITEM_TYPE_APPLICATION
|| itemInfo.itemType == ITEM_TYPE_SHORTCUT
|| itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(),
itemInfo.user);
final List<ComponentKeyMapper> apps = getCurrentState().apps;
return IntStream.range(0, apps.size())
.filter(index -> key.equals(apps.get(index).getComponentKey()))
.findFirst();
}
return OptionalInt.empty();
}
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
* {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
* {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
* {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
*/
public static void fillInPredictedRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
return;
}
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
.ifPresent((rank) -> target.predictedRank = 0 - rank);
}
public static class PredictionState {
public boolean isEnabled;
public List<ComponentKeyMapper> apps;
}
}

View File

@ -13,175 +13,76 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.appprediction;
package com.android.launcher3.model;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FolderContainer;
import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.pm.UserCache;
import com.android.quickstep.logging.StatsLogCompatManager;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Subclass of app tracker which publishes the data to the prediction engine and gets back results.
* Utility class to track stats log and emit corresponding app events
*/
@TargetApi(Build.VERSION_CODES.Q)
public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer {
@TargetApi(Build.VERSION_CODES.R)
public class AppEventProducer implements StatsLogConsumer {
private static final String TAG = "PredictionAppTracker";
private static final boolean DBG = false;
private static final int MSG_LAUNCH = 0;
private static final int MSG_INIT = 0;
private static final int MSG_DESTROY = 1;
private static final int MSG_LAUNCH = 2;
private static final int MSG_PREDICT = 3;
protected final Context mContext;
private final Context mContext;
private final Handler mMessageHandler;
private final Consumer<AppTargetEvent> mCallback;
// Accessed only on worker thread
private AppPredictor mHomeAppPredictor;
public PredictionAppTracker(Context context) {
public AppEventProducer(Context context, Consumer<AppTargetEvent> callback) {
mContext = context;
mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
mMessageHandler.sendEmptyMessage(MSG_INIT);
}
@UiThread
private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
// Reinitialize everything
mMessageHandler.sendEmptyMessage(MSG_INIT);
}
}
@WorkerThread
private void destroy() {
if (mHomeAppPredictor != null) {
mHomeAppPredictor.destroy();
mHomeAppPredictor = null;
}
StatsLogCompatManager.LOGS_CONSUMER.remove(this);
}
@WorkerThread
private AppPredictor createPredictor(Client client, int count) {
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
if (apm == null) {
return null;
}
AppPredictor predictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mContext)
.setUiSurface(client.id)
.setPredictedTargetCount(count)
.setExtras(getAppPredictionContextExtras(client))
.build());
predictor.registerPredictionUpdates(mContext.getMainExecutor(),
PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client));
predictor.requestPredictionUpdate();
return predictor;
}
/**
* Override to add custom extras.
*/
@WorkerThread
@Nullable
public Bundle getAppPredictionContextExtras(Client client) {
return null;
mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage);
mCallback = callback;
}
@WorkerThread
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_INIT: {
// Destroy any existing clients
destroy();
// Initialize the clients
int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
mHomeAppPredictor = createPredictor(Client.HOME, count);
StatsLogCompatManager.LOGS_CONSUMER.add(this);
return true;
}
case MSG_DESTROY: {
destroy();
return true;
}
case MSG_LAUNCH: {
if (mHomeAppPredictor != null) {
mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj);
}
return true;
}
case MSG_PREDICT: {
if (mHomeAppPredictor != null) {
mHomeAppPredictor.requestPredictionUpdate();
}
mCallback.accept((AppTargetEvent) msg.obj);
return true;
}
}
return false;
}
@Override
@UiThread
public void onReturnedToHome() {
String client = Client.HOME.id;
mMessageHandler.removeMessages(MSG_PREDICT, client);
Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget();
if (DBG) {
Log.d(TAG, String.format("Sent immediate message to update %s", client));
}
}
@AnyThread
private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) {
AppTarget target = toAppTarget(atomInfo);

View File

@ -0,0 +1,117 @@
/*
* 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.launcher3.model;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Task to update model as a result of predicted apps update
*/
public class PredictionUpdateTask extends BaseModelUpdateTask {
private final List<AppTarget> mTargets;
private final int mContainerId;
PredictionUpdateTask(int containerId, List<AppTarget> targets) {
mContainerId = containerId;
mTargets = targets;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
// TODO: persist the whole list
Utilities.getDevicePrefs(app.getContext()).edit()
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
FixedContainerItems fci;
synchronized (dataModel) {
fci = dataModel.extraItems.get(mContainerId);
if (fci == null) {
return;
}
}
Set<UserHandle> usersForChangedShortcuts = new HashSet<>(fci.items.stream()
.filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
.map(info -> info.user)
.collect(Collectors.toSet()));
fci.items.clear();
for (AppTarget target : mTargets) {
WorkspaceItemInfo itemInfo;
ShortcutInfo si = target.getShortcutInfo();
if (si != null) {
usersForChangedShortcuts.add(si.getUserHandle());
itemInfo = new WorkspaceItemInfo(si, app.getContext());
app.getIconCache().getShortcutIcon(itemInfo, si);
} else {
String className = target.getClassName();
if (COMPONENT_CLASS_MARKER.equals(className)) {
// TODO: Implement this
continue;
}
ComponentName cn = new ComponentName(target.getPackageName(), className);
UserHandle user = target.getUser();
itemInfo = apps.data.stream()
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
.map(AppInfo::makeWorkspaceItem)
.findAny()
.orElseGet(() -> {
LauncherActivityInfo lai = app.getContext()
.getSystemService(LauncherApps.class)
.resolveActivity(AppInfo.makeLaunchIntent(cn), user);
if (lai == null) {
return null;
}
AppInfo ai = new AppInfo(app.getContext(), lai, user);
app.getIconCache().getTitleAndIcon(ai, lai, false);
return ai.makeWorkspaceItem();
});
if (itemInfo == null) {
continue;
}
}
itemInfo.container = mContainerId;
fci.items.add(itemInfo);
}
bindExtraContainerItems(fci);
usersForChangedShortcuts.forEach(
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.launcher3.model;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.content.Context;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.util.Executors;
import com.android.quickstep.logging.StatsLogCompatManager;
import java.util.List;
/**
* Model delegate which loads prediction items
*/
public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
private AppPredictor mAllAppsPredictor;
private boolean mActive = false;
public QuickstepModelDelegate(Context context) {
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
mIDP.addOnChangeListener(this);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
}
@Override
public void loadItems() {
// TODO: Implement caching and preloading
super.loadItems();
mDataModel.extraItems.put(
CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION));
mActive = true;
recreatePredictors();
}
@Override
public void validateData() {
super.validateData();
if (mAllAppsPredictor != null) {
mAllAppsPredictor.requestPredictionUpdate();
}
}
@Override
public void destroy() {
super.destroy();
mActive = false;
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
destroyPredictors();
mIDP.removeOnChangeListener(this);
}
private void destroyPredictors() {
if (mAllAppsPredictor != null) {
mAllAppsPredictor.destroy();
mAllAppsPredictor = null;
}
}
@WorkerThread
private void recreatePredictors() {
destroyPredictors();
if (!mActive) {
return;
}
Context context = mApp.getContext();
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
int count = mIDP.numAllAppsColumns;
mAllAppsPredictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("home")
.setPredictedTargetCount(count)
.build());
mAllAppsPredictor.registerPredictionUpdates(
Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged);
mAllAppsPredictor.requestPredictionUpdate();
}
private void onAllAppsPredictionChanged(List<AppTarget> targets) {
mApp.getModel().enqueueModelUpdateTask(
new PredictionUpdateTask(CONTAINER_PREDICTION, targets));
}
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
// Reinitialize everything
Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
}
}
private void onAppTargetEvent(AppTargetEvent event) {
if (mAllAppsPredictor != null) {
mAllAppsPredictor.notifyAppTargetEvent(event);
}
}
}

View File

@ -17,6 +17,9 @@ package com.android.launcher3.uioverrides;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@ -37,16 +40,18 @@ import android.view.View;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@ -79,7 +84,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
import java.util.Objects;
import java.util.stream.Stream;
public class QuickstepLauncher extends BaseQuickstepLauncher {
@ -91,6 +96,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
private FixedContainerItems mAllAppsPredictions;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -111,8 +118,23 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
StatsLogger logger = getStatsLogManager()
.logger().withItemInfo(info).withInstanceId(instanceId);
OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
allAppsRank.ifPresent(logger::withRank);
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
|| info.itemType == ITEM_TYPE_SHORTCUT
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
if (targetInfo.itemType == info.itemType
&& targetInfo.user.equals(info.user)
&& Objects.equals(targetInfo.getIntent(), info.getIntent())) {
logger.withRank(i);
break;
}
}
}
logger.log(LAUNCHER_APP_LAUNCH_TAP);
if (mHotseatPredictionController != null) {
@ -199,6 +221,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
}
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
.setPredictedApps(item.items);
}
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@ -41,7 +41,6 @@ import com.android.launcher3.R;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
import com.android.launcher3.statemanager.StateManager;

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2018 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.quickstep.logging;
import android.content.Context;
import androidx.annotation.NonNull;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import java.util.ArrayList;
/**
* This class handles AOSP MetricsLogger function calls and logging around
* quickstep interactions and app launches.
*/
@SuppressWarnings("unused")
public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
public static final int ALL_APPS_PREDICTION_TIPS = 2;
private static final String TAG = "UserEventDispatcher";
public UserEventDispatcherAppPredictionExtension(Context context) {
super(context);
}
@Override
protected void onFillInLogContainerData(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
@NonNull ArrayList<LauncherLogProto.Target> targets) {
PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
}
}

View File

@ -1,162 +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.quickstep;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.os.Process;
import android.view.View;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.model.AppLaunchTracker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class AppPredictionsUITests extends AbstractQuickStepTest {
private LauncherActivityInfo mSampleApp1;
private LauncherActivityInfo mSampleApp2;
private LauncherActivityInfo mSampleApp3;
private AppPredictor.Callback mCallback;
@Before
public void setUp() throws Exception {
super.setUp();
List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle());
mSampleApp1 = activities.get(0);
mSampleApp2 = activities.get(1);
mSampleApp3 = activities.get(2);
// Disable app tracker
AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
PredictionUiStateManager.INSTANCE.initializeForTesting(null);
mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
Client.HOME);
mDevice.setOrientationNatural();
}
@After
public void tearDown() throws Throwable {
AppLaunchTracker.INSTANCE.initializeForTesting(null);
PredictionUiStateManager.INSTANCE.initializeForTesting(null);
mDevice.unfreezeRotation();
}
/**
* Test that prediction UI is updated as soon as we get predictions from the system
*/
@Test
public void testPredictionExistsInAllApps() {
mLauncher.pressHome().switchToAllApps();
// Dispatch an update
sendPredictionUpdate(mSampleApp1, mSampleApp2);
// The first update should apply immediately.
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> getPredictedApp(launcher).size() == 2);
}
/**
* Test that prediction update is deferred if it is already visible
*/
@Test
public void testPredictionsDeferredUntilHome() {
mDevice.pressHome();
sendPredictionUpdate(mSampleApp1, mSampleApp2);
mLauncher.pressHome().switchToAllApps();
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> getPredictedApp(launcher).size() == 2);
// Update predictions while all-apps is visible
sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3);
assertEquals(2, getFromLauncher(this::getPredictedApp).size());
// Go home and go back to all-apps
mLauncher.pressHome().switchToAllApps();
assertEquals(3, getFromLauncher(this::getPredictedApp).size());
}
@Test
public void testPredictionsDisabled() {
mDevice.pressHome();
sendPredictionUpdate();
mLauncher.pressHome().switchToAllApps();
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> launcher.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE);
assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext)
.getCurrentState().isEnabled);
}
public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class);
ArrayList<BubbleTextView> predictedAppViews = new ArrayList<>();
for (int i = 0; i < container.getChildCount(); i++) {
View view = container.getChildAt(i);
if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) {
predictedAppViews.add((BubbleTextView) view);
}
}
return predictedAppViews;
}
private void sendPredictionUpdate(LauncherActivityInfo... activities) {
getOnUiThread(() -> {
List<AppTarget> targets = new ArrayList<>(activities.length);
for (LauncherActivityInfo info : activities) {
ComponentName cn = info.getComponentName();
AppTarget target = new AppTarget.Builder(
new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
.setClassName(cn.getClassName())
.build();
targets.add(target);
}
mCallback.onTargetsAvailable(targets);
return null;
});
}
}

View File

@ -70,6 +70,7 @@
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
<string name="prediction_model_class" translatable="false"></string>
<string name="model_delegate_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />

View File

@ -119,7 +119,6 @@ import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.AppInfo;
@ -911,14 +910,12 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
// Process any items that were added while Launcher was away.
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
// Refresh shortcuts if the permission changed.
mModel.refreshShortcutsIfRequired();
mModel.validateModelDataOnResume();
// Set the notification listener and fetch updated notifications when we resume
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);

View File

@ -128,7 +128,7 @@ public class LauncherAppState {
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext));
mPredictionModel = PredictionModel.newInstance(mContext);
}
@ -157,6 +157,7 @@ public class LauncherAppState {
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
mModel.destroy();
if (mModelChangeReceiver != null) {
mContext.unregisterReceiver(mModelChangeReceiver);
}

View File

@ -20,7 +20,6 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
import android.content.Intent;
@ -46,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
@ -112,20 +112,22 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
*/
private final BgDataModel mBgDataModel = new BgDataModel();
private final ModelDelegate mModelDelegate;
// Runnable to check if the shortcuts permission has changed.
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
private final Runnable mDataValidationCheck = new Runnable() {
@Override
public void run() {
if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
!= mBgAllAppsList.hasShortcutHostPermission()) {
forceReload();
if (mModelLoaded) {
mModelDelegate.validateData();
}
}
};
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
}
/**
@ -217,6 +219,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
}
}
/**
* Called when the model is destroyed
*/
public void destroy() {
MODEL_EXECUTOR.execute(mModelDelegate::destroy);
}
public void onBroadcastIntent(Intent intent) {
if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
@ -372,7 +381,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
mLoaderTask = new LoaderTask(
mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
@ -491,9 +501,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
* Current implementation simply reloads the workspace, but it can be optimized to
* use partial updates similar to {@link UserCache}
*/
public void refreshShortcutsIfRequired() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
public void validateModelDataOnResume() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
MODEL_EXECUTOR.post(mDataValidationCheck);
}
/**

View File

@ -74,6 +74,7 @@ import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.AppInfo;
@ -579,7 +580,7 @@ public class LauncherPreviewRenderer {
private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
super(app, null, new BgDataModel(), null);
super(app, null, new BgDataModel(), new ModelDelegate(), null);
}
@Override

View File

@ -1,33 +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.launcher3.model;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
/**
* Callback for receiving various app launch events
*/
public class AppLaunchTracker implements ResourceBasedOverride {
public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
public void onReturnedToHome() { }
}

View File

@ -108,13 +108,6 @@ public class BgDataModel {
*/
public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
/**
* @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
* @see Callbacks#FLAG_QUIET_MODE_ENABLED
* @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
*/
public int flags;
/**
* Maps all launcher activities to counts of their shortcuts.
*/

View File

@ -109,6 +109,7 @@ public class LoaderTask implements Runnable {
protected final LauncherAppState mApp;
private final AllAppsList mBgAllAppsList;
protected final BgDataModel mBgDataModel;
private final ModelDelegate mModelDelegate;
private FirstScreenBroadcast mFirstScreenBroadcast;
@ -128,10 +129,11 @@ public class LoaderTask implements Runnable {
private boolean mStopped;
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
LoaderResults results) {
ModelDelegate modelDelegate, LoaderResults results) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
mBgDataModel = dataModel;
mModelDelegate = modelDelegate;
mResults = results;
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
@ -767,6 +769,9 @@ public class LoaderTask implements Runnable {
IOUtils.closeSilently(c);
}
// Load delegate items
mModelDelegate.loadItems();
// Break early if we've stopped loading
if (mStopped) {
mBgDataModel.clear();

View File

@ -0,0 +1,75 @@
/*
* 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.launcher3.model;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.util.ResourceBasedOverride;
/**
* Class to extend LauncherModel functionality to provide extra data
*/
public class ModelDelegate implements ResourceBasedOverride {
/**
* Creates and initializes a new instance of the delegate
*/
public static ModelDelegate newInstance(
Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) {
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
delegate.mApp = app;
delegate.mAppsList = appsList;
delegate.mDataModel = dataModel;
return delegate;
}
protected LauncherAppState mApp;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
public ModelDelegate() { }
/**
* Called periodically to validate and update any data
*/
@WorkerThread
public void validateData() {
if (hasShortcutsPermission(mApp.getContext())
!= mAppsList.hasShortcutHostPermission()) {
mApp.getModel().forceReload();
}
}
/**
* Load delegate items if any in the data model
*/
@WorkerThread
public void loadItems() { }
/**
* Called when the delegate is no loner needed
*/
@WorkerThread
public void destroy() { }
}

View File

@ -53,7 +53,6 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.common.WidgetUtils;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
@ -272,8 +271,6 @@ public abstract class AbstractLauncherUiTest {
}
mLauncherPid = 0;
// Disable app tracker
AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();