Merge "Moving all-apps predictions to Launcher model" into ub-launcher3-master
This commit is contained in:
commit
89b670056f
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() { }
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() { }
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue