Merging from ub-launcher3-master @ build 6799015

Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-master_master_6799015.html

Change-Id: Ib9d52ba333f2982d2420cab42a0935e8e8a303b3
This commit is contained in:
Andy Wickham
2020-08-28 22:22:28 +00:00
87 changed files with 846 additions and 1949 deletions
+1
View File
@@ -28,6 +28,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.recyclerview_recyclerview \
androidx.dynamicanimation_dynamicanimation \
androidx.preference_preference \
androidx.slice_slice-view \
iconloader_base
LOCAL_STATIC_JAVA_LIBRARIES := \
-2
View File
@@ -27,8 +27,6 @@
<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>
@@ -26,12 +26,10 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.CancellationSignal;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -40,14 +38,12 @@ import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.views.OverviewActionsView;
@@ -73,7 +69,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
Float.intBitsToFloat(arg1), arg2 != 0);
private OverviewActionsView mActionsView;
protected HotseatPredictionController mHotseatPredictionController;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -221,11 +216,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
return mDepthController;
}
@Override
protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
@Override
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
QuickstepAppTransitionManagerImpl appTransitionManager =
@@ -314,13 +304,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
Stream.of(WellbeingModel.SHORTCUT_FACTORY));
}
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
@@ -19,7 +19,6 @@ package com.android.launcher3.appprediction;
import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
import android.os.UserManager;
@@ -31,7 +30,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.views.ArrowTipView;
import com.android.systemui.shared.system.LauncherEventUtil;
/**
* ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
@@ -57,8 +55,7 @@ public class AllAppsTipView {
floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
ALL_APPS_PREDICTION_TIPS);
// TODO: add log to WW
});
arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
@@ -1,63 +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.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.ComponentKey;
public class ComponentKeyMapper {
protected final ComponentKey componentKey;
private final DynamicItemCache mCache;
public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
componentKey = key;
mCache = cache;
}
public String getPackage() {
return componentKey.componentName.getPackageName();
}
public String getComponentClass() {
return componentKey.componentName.getClassName();
}
public ComponentKey getComponentKey() {
return componentKey;
}
@Override
public String toString() {
return componentKey.toString();
}
public ItemInfoWithIcon getApp(AllAppsStore store) {
AppInfo item = store.getApp(componentKey);
if (item != null) {
return item;
} else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
return mCache.getInstantApp(componentKey.componentName.getPackageName());
} else {
return mCache.getShortcutInfo(componentKey);
}
}
}
@@ -1,268 +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 android.content.pm.PackageManager.MATCH_INSTANT;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Utility class which loads and caches predicted items like instant apps and shortcuts, before
* they can be displayed on the UI
*/
public class DynamicItemCache {
private static final String TAG = "DynamicItemCache";
private static final boolean DEBUG = false;
private static final String DEFAULT_URL = "default-url";
private static final int BG_MSG_LOAD_SHORTCUTS = 1;
private static final int BG_MSG_LOAD_INSTANT_APPS = 2;
private static final int UI_MSG_UPDATE_SHORTCUTS = 1;
private static final int UI_MSG_UPDATE_INSTANT_APPS = 2;
private final Context mContext;
private final Handler mWorker;
private final Handler mUiHandler;
private final InstantAppResolver mInstantAppResolver;
private final Runnable mOnUpdateCallback;
private final IconCache mIconCache;
private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
private final Map<String, InstantAppItemInfo> mInstantApps;
public DynamicItemCache(Context context, Runnable onUpdateCallback) {
mContext = context;
mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
mInstantAppResolver = InstantAppResolver.newInstance(context);
mOnUpdateCallback = onUpdateCallback;
mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
mShortcuts = new HashMap<>();
mInstantApps = new HashMap<>();
}
public void cacheItems(List<ShortcutKey> shortcutKeys, List<String> pkgNames) {
if (!shortcutKeys.isEmpty()) {
mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS);
Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget();
}
if (!pkgNames.isEmpty()) {
mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS);
Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget();
}
}
private boolean handleWorkerMessage(Message msg) {
switch (msg.what) {
case BG_MSG_LOAD_SHORTCUTS: {
List<ShortcutKey> shortcutKeys = msg.obj != null ?
(List<ShortcutKey>) msg.obj : Collections.EMPTY_LIST;
Map<ShortcutKey, WorkspaceItemInfo> shortcutKeyAndInfos = new ArrayMap<>();
for (ShortcutKey shortcutKey : shortcutKeys) {
WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey);
if (workspaceItemInfo != null) {
shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo);
}
}
Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos)
.sendToTarget();
return true;
}
case BG_MSG_LOAD_INSTANT_APPS: {
List<String> pkgNames = msg.obj != null ?
(List<String>) msg.obj : Collections.EMPTY_LIST;
List<InstantAppItemInfo> instantAppItemInfos = new ArrayList<>();
for (String pkgName : pkgNames) {
InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName);
if (instantAppItemInfo != null) {
instantAppItemInfos.add(instantAppItemInfo);
}
}
Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos)
.sendToTarget();
return true;
}
}
return false;
}
private boolean handleUiMessage(Message msg) {
switch (msg.what) {
case UI_MSG_UPDATE_SHORTCUTS: {
mShortcuts.clear();
mShortcuts.putAll((Map<ShortcutKey, WorkspaceItemInfo>) msg.obj);
mOnUpdateCallback.run();
return true;
}
case UI_MSG_UPDATE_INSTANT_APPS: {
List<InstantAppItemInfo> instantAppItemInfos = (List<InstantAppItemInfo>) msg.obj;
mInstantApps.clear();
for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) {
mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(),
instantAppItemInfo);
}
mOnUpdateCallback.run();
if (DEBUG) {
Log.d(TAG, String.format("Cache size: %d, Cache: %s",
mInstantApps.size(), mInstantApps.toString()));
}
return true;
}
}
return false;
}
@WorkerThread
private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
if (!details.isEmpty()) {
WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
mIconCache.getShortcutIcon(si, details.get(0));
return si;
}
if (DEBUG) {
Log.d(TAG, "No shortcut found: " + shortcutKey.toString());
}
return null;
}
private InstantAppItemInfo loadInstantApp(String pkgName) {
PackageManager pm = mContext.getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
if (!mInstantAppResolver.isInstantApp(ai)) {
return null;
}
} catch (PackageManager.NameNotFoundException e) {
return null;
}
String url = retrieveDefaultUrl(pkgName, pm);
if (url == null) {
Log.w(TAG, "no default-url available for pkg " + pkgName);
return null;
}
Intent intent = new Intent(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.parse(url));
InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
iconCache.getTitleAndIcon(info, false);
if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
return null;
}
return info;
}
@Nullable
public static String retrieveDefaultUrl(String pkgName, PackageManager pm) {
Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA);
String url = null;
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo.activityInfo.metaData != null
&& resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) {
url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL);
}
}
return url;
}
@UiThread
public InstantAppItemInfo getInstantApp(String pkgName) {
return mInstantApps.get(pkgName);
}
@MainThread
public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
return mShortcuts.get(key);
}
/**
* requests and caches icons for app targets
*/
public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
List<String> instantAppsToLoad = new ArrayList<>();
List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
int total = componentKeyMappers.size();
for (int i = 0, count = 0; i < total && count < itemCount; i++) {
ComponentKeyMapper mapper = componentKeyMappers.get(i);
// Update instant apps
if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
instantAppsToLoad.add(mapper.getPackage());
count++;
} else if (mapper.getComponentKey() instanceof ShortcutKey) {
shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
count++;
} else {
// Reload high res icon
AppInfo info = (AppInfo) mapper.getApp(appsStore);
if (info != null) {
if (info.usingLowResIcon()) {
mIconCache.updateIconInBackground(callback, info);
}
count++;
}
}
}
cacheItems(shortcutsToLoad, instantAppsToLoad);
}
}
@@ -47,35 +47,27 @@ import java.util.stream.IntStream;
*/
public class HotseatEduController {
public static final String HOTSEAT_EDU_ACTION =
"com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
public static final String SETTINGS_ACTION =
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
private final Launcher mLauncher;
private final Hotseat mHotseat;
private HotseatRestoreHelper mRestoreHelper;
private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog;
private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
private IntArray mNewScreens = null;
private Runnable mOnOnboardingComplete;
HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
HotseatEduController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mRestoreHelper = restoreHelper;
mOnOnboardingComplete = runnable;
}
/**
* Checks what type of migration should be used and migrates hotseat
*/
void migrate() {
if (mRestoreHelper != null) {
mRestoreHelper.createBackup();
}
HotseatRestoreHelper.createBackup(mLauncher);
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
migrateToFolder();
} else {
@@ -227,7 +219,7 @@ public class HotseatEduController {
}
void finishOnboarding() {
mOnOnboardingComplete.run();
mLauncher.getModel().onWorkspaceUiChanged();
}
void showDimissTip() {
@@ -40,7 +40,6 @@ import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.AbstractSlideInView;
import java.util.List;
@@ -121,16 +120,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
handleClose(true);
}
@Override
public void logActionCommand(int command) {
// Since this is on-boarding popup, it is not a user controlled action.
}
@Override
public int getLogContainerType() {
return LauncherLogProto.ContainerType.TIP;
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ON_BOARD_POPUP) != 0;
@@ -15,23 +15,17 @@
*/
package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
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.ComponentName;
import android.os.Process;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
@@ -44,77 +38,51 @@ import com.android.launcher3.DropTarget;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
import com.android.launcher3.appprediction.DynamicItemCache;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.Snackbar;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
/**
* Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
* pinning of predicted apps and manages replacement of predicted apps with user drag.
*/
public class HotseatPredictionController implements DragController.DragListener,
View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
IconCache.ItemInfoUpdateReceiver, DragSource {
SystemShortcut.Factory<QuickstepLauncher>, InvariantDeviceProfile.OnIDPChangeListener,
DragSource {
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
private static final String PREDICTION_CLIENT = "hotseat";
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
private int mPredictedSpotsCount = 0;
private Launcher mLauncher;
private final Hotseat mHotseat;
private final HotseatRestoreHelper mRestoreHelper;
private List<ItemInfo> mPredictedItems = Collections.emptyList();
private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
private DynamicItemCache mDynamicItemCache;
private final HotseatPredictionModel mPredictionModel;
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
private AnimatorSet mIconRemoveAnimators;
private boolean mUIUpdatePaused = false;
private boolean mIsDestroyed = false;
private boolean mDragInProgress = false;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
@@ -130,26 +98,23 @@ public class HotseatPredictionController implements DragController.DragListener,
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
// Start the drag
mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
// Use a new itemInfo so that the original predicted item is stable
WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
v.setVisibility(View.INVISIBLE);
mLauncher.getWorkspace().beginDragShared(
v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
return true;
};
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
LauncherAppState appState = LauncherAppState.getInstance(launcher);
mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel();
mAllAppsStore.addUpdateListener(this);
mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
mLauncher.getDragController().addDragListener(this);
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
mRestoreHelper = new HotseatRestoreHelper(mLauncher);
if (mHotseat.isAttachedToWindow()) {
onViewAttachedToWindow(mHotseat);
}
}
/**
@@ -157,7 +122,7 @@ public class HotseatPredictionController implements DragController.DragListener,
*/
public void showEdu() {
mLauncher.getStateManager().goToState(NORMAL, true, () -> {
if (mComponentKeyMappers.isEmpty()) {
if (mPredictedItems.isEmpty()) {
// launcher has empty predictions set
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
R.string.hotseat_prediction_settings, null,
@@ -165,10 +130,10 @@ public class HotseatPredictionController implements DragController.DragListener,
} else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
showDiscoveryTip();
} else {
HotseatEduController eduController = new HotseatEduController(mLauncher,
mRestoreHelper,
this::createPredictor);
eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
HotseatEduController eduController = new HotseatEduController(mLauncher);
eduController.setPredictedApps(mPredictedItems.stream()
.map(i -> (WorkspaceItemInfo) i)
.collect(Collectors.toList()));
eduController.showEdu();
}
});
@@ -192,17 +157,7 @@ public class HotseatPredictionController implements DragController.DragListener,
* Returns if hotseat client has predictions
*/
public boolean hasPredictions() {
return !mComponentKeyMappers.isEmpty();
}
@Override
public void onViewAttachedToWindow(View view) {
mLauncher.getDragController().addDragListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
mLauncher.getDragController().removeDragListener(this);
return !mPredictedItems.isEmpty();
}
private void fillGapsWithPrediction() {
@@ -210,15 +165,10 @@ public class HotseatPredictionController implements DragController.DragListener,
}
private void fillGapsWithPrediction(boolean animate, Runnable callback) {
if (mUIUpdatePaused || mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) {
// Safely ignore update as AppsList is not ready yet. This will called again once
// apps are ready (HotseatPredictionController#onAppsUpdated)
if (mUIUpdatePaused || mDragInProgress) {
return;
}
int predictionIndex = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
@@ -240,14 +190,15 @@ public class HotseatPredictionController implements DragController.DragListener,
if (child != null && !isPredictedIcon(child)) {
continue;
}
if (predictedApps.size() <= predictionIndex) {
if (mPredictedItems.size() <= predictionIndex) {
// Remove predicted apps from the past
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
continue;
}
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
WorkspaceItemInfo predictedItem =
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
@@ -257,7 +208,6 @@ public class HotseatPredictionController implements DragController.DragListener,
}
preparePredictionInfo(predictedItem, rank);
}
mPredictedSpotsCount = predictionIndex;
bindItems(newItems, animate, callback);
}
@@ -285,13 +235,7 @@ public class HotseatPredictionController implements DragController.DragListener,
* Unregisters callbacks and frees resources
*/
public void destroy() {
mIsDestroyed = true;
mAllAppsStore.removeUpdateListener(this);
mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
mHotseat.removeOnAttachStateChangeListener(this);
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
}
/**
@@ -305,97 +249,14 @@ public class HotseatPredictionController implements DragController.DragListener,
}
/**
* Creates App Predictor with all the current apps pinned on the hotseat
* Sets or updates the predicted items
*/
public void createPredictor() {
AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
public void setPredictedItems(FixedContainerItems items) {
mPredictedItems = items.items;
if (mPredictedItems.isEmpty()) {
HotseatRestoreHelper.restoreBackup(mLauncher);
}
if (mAppPredictor != null) {
mAppPredictor.destroy();
mAppPredictor = null;
}
WeakReference<HotseatPredictionController> controllerRef = new WeakReference<>(this);
mPredictionModel.createBundle(bundle -> {
if (mIsDestroyed) return;
mAppPredictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mLauncher)
.setUiSurface(PREDICTION_CLIENT)
.setPredictedTargetCount(mHotSeatItemsCount)
.setExtras(bundle)
.build());
mAppPredictor.registerPredictionUpdates(
mLauncher.getApplicationContext().getMainExecutor(),
list -> {
if (controllerRef.get() != null) {
controllerRef.get().setPredictedApps(list);
}
});
mAppPredictor.requestPredictionUpdate();
});
setPauseUIUpdate(false);
}
/**
* Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
*/
public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
if (hasPredictions() && mAppPredictor != null) {
mAppPredictor.requestPredictionUpdate();
fillGapsWithPrediction();
return;
}
int count = Math.min(ranks.size(), apps.size());
List<WorkspaceItemInfo> items = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
preparePredictionInfo(item, ranks.get(i));
items.add(item);
mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache));
}
updateDependencies();
bindItems(items, false, null);
}
private void setPredictedApps(List<AppTarget> appTargets) {
mComponentKeyMappers.clear();
if (appTargets.isEmpty()) {
mRestoreHelper.restoreBackup();
}
StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
ArrayList<ComponentKey> componentKeys = new ArrayList<>();
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());
}
componentKeys.add(key);
predictionLog.append(key.toString());
predictionLog.append(",rank:");
predictionLog.append(appTarget.getRank());
predictionLog.append("\n");
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
predictionLog.append("]");
if (Utilities.IS_DEBUG_DEVICE) {
HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
}
updateDependencies();
fillGapsWithPrediction();
mPredictionModel.cachePredictionComponentKeys(componentKeys);
}
private void updateDependencies() {
mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
mHotSeatItemsCount);
}
/**
@@ -414,42 +275,9 @@ public class HotseatPredictionController implements DragController.DragListener,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
icon.pin(workspaceItemInfo);
AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo);
if (appTarget != null) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
AppTargetEvent.ACTION_PIN, workspaceItemInfo));
}
}
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
List<ComponentKeyMapper> components) {
AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
if (allAppsStore.getApps().length == 0) {
return Collections.emptyList();
}
List<WorkspaceItemInfo> predictedApps = new ArrayList<>();
for (ComponentKeyMapper mapper : components) {
ItemInfoWithIcon info = mapper.getApp(allAppsStore);
if (info instanceof AppInfo) {
WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
predictedApps.add(predictedApp);
} else if (info instanceof WorkspaceItemInfo) {
WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
predictedApps.add(predictedApp);
} else {
if (DEBUG) {
Log.e(TAG, "Predicted app not found: " + mapper);
}
}
// Stop at the number of hotseat items
if (predictedApps.size() == mHotSeatItemsCount) {
break;
}
}
return predictedApps;
mLauncher.getStatsLogManager().logger()
.withItemInfo(workspaceItemInfo)
.log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
}
private List<PredictedAppIcon> getPredictedIcons() {
@@ -465,7 +293,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
ItemInfo draggedInfo) {
DropTarget.DragObject dragObject) {
if (mIconRemoveAnimators != null) {
mIconRemoveAnimators.end();
}
@@ -475,7 +303,7 @@ public class HotseatPredictionController implements DragController.DragListener,
if (!icon.isEnabled()) {
continue;
}
if (icon.getTag().equals(draggedInfo)) {
if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
mHotseat.removeView(icon);
continue;
}
@@ -497,84 +325,23 @@ public class HotseatPredictionController implements DragController.DragListener,
mIconRemoveAnimators.start();
}
private void notifyItemAction(AppTargetEvent event) {
if (mAppPredictor != null) {
mAppPredictor.notifyAppTargetEvent(event);
}
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
mDragObject = dragObject;
removePredictedApps(mOutlineDrawings, dragObject);
if (mOutlineDrawings.isEmpty()) return;
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
mHotseat.addDelegatedCellDrawing(outlineDrawing);
}
mDragInProgress = true;
mHotseat.invalidate();
}
/**
* Unpins pinned app when it's converted into a folder
*/
public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
AppTargetEvent.ACTION_PIN, folderInfo));
}
// using folder info with isTrackedForPrediction as itemInfo.container is already changed
// to folder by this point
if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
AppTargetEvent.ACTION_UNPIN, folderInfo
));
}
}
/**
* Pins workspace item created when all folder items are removed but one
*/
public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
AppTargetEvent.ACTION_UNPIN, folderInfo));
}
if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
AppTargetEvent.ACTION_PIN, itemInfo));
}
}
@Override
public void onDragEnd() {
if (mDragObject == null) {
return;
}
ItemInfo dragInfo = mDragObject.dragInfo;
if (mDragObject.isMoved()) {
AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo);
//always send pin event first to prevent AiAi from predicting an item moved within
// the same page
if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
AppTargetEvent.ACTION_PIN, dragInfo));
}
if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(
mDragObject.originalDragInfo)) {
notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo));
}
}
mDragObject = null;
mDragInProgress = false;
fillGapsWithPrediction(true, this::removeOutlineDrawings);
}
@Nullable
@Override
public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
@@ -604,19 +371,7 @@ public class HotseatPredictionController implements DragController.DragListener,
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
this.mHotSeatItemsCount = profile.numHotseatIcons;
createPredictor();
}
}
@Override
public void onAppsUpdated() {
fillGapsWithPrediction();
}
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
this.mHotSeatItemsCount = profile.numHotseatIcons;
}
@Override
@@ -624,12 +379,6 @@ public class HotseatPredictionController implements DragController.DragListener,
//Does nothing
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> parents) {
mHotseat.fillInLogContainerData(childInfo, child, parents);
}
/**
* Logs rank info based on current list of predicted items
*/
@@ -643,17 +392,20 @@ public class HotseatPredictionController implements DragController.DragListener,
+ ",launchLocation:" + itemInfo.container);
}
if (itemInfo.getTargetComponent() == null || itemInfo.user == null) {
ComponentName targetCN = itemInfo.getTargetComponent();
if (targetCN == null) {
return;
}
final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter(index -> key.equals(predictedApps.get(index).getComponentKey()))
.findFirst();
if (!rank.isPresent()) {
int rank = -1;
for (int i = mPredictedItems.size() - 1; i >= 0; i--) {
ItemInfo info = mPredictedItems.get(i);
if (targetCN.equals(info.getTargetComponent()) && itemInfo.user.equals(info.user)) {
rank = i;
break;
}
}
if (rank < 0) {
return;
}
@@ -666,11 +418,11 @@ public class HotseatPredictionController implements DragController.DragListener,
PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
containerBuilder.setCardinality(cardinality);
if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
containerBuilder.setIndex(rank.getAsInt());
containerBuilder.setIndex(rank);
}
mLauncher.getStatsLogManager().logger()
.withInstanceId(instanceId)
.withRank(rank.getAsInt())
.withRank(rank)
.withContainerInfo(ContainerInfo.newBuilder()
.setPredictedHotseatContainer(containerBuilder)
.build())
@@ -691,30 +443,6 @@ public class HotseatPredictionController implements DragController.DragListener,
}
}
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
*/
public static void encodeHotseatLayoutIntoPredictionRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
if (launcher == null || launcher.getHotseatPredictionController() == null
|| itemInfo.getTargetComponent() == null) {
return;
}
HotseatPredictionController controller = launcher.getHotseatPredictionController();
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst();
target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+ (rank.isPresent() ? rank.getAsInt() + 1 : 0);
}
private static boolean isPredictedIcon(View view) {
return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
@@ -24,13 +24,10 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Workspace;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.PredictionModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -38,55 +35,48 @@ import com.android.launcher3.shortcuts.ShortcutKey;
import java.util.ArrayList;
import java.util.Locale;
import java.util.function.Consumer;
/**
* Model helper for app predictions in workspace
*/
public class HotseatPredictionModel extends PredictionModel {
public class HotseatPredictionModel {
private static final String APP_LOCATION_HOTSEAT = "hotseat";
private static final String APP_LOCATION_WORKSPACE = "workspace";
private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
public HotseatPredictionModel(Context context) { }
/**
* Creates and returns bundle using workspace items and cached items
* Creates and returns bundle using workspace items
*/
public void createBundle(Consumer<Bundle> cb) {
LauncherAppState appState = LauncherAppState.getInstance(mContext);
appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> events = new ArrayList<>();
ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
workspaceItems.addAll(dataModel.appWidgets);
for (ItemInfo item : workspaceItems) {
AppTarget target = getAppTargetFromInfo(item);
if (target != null && !isTrackedForPrediction(item)) continue;
events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
}
ArrayList<AppTarget> currentTargets = new ArrayList<>();
for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
AppTarget target = getAppTargetFromInfo(itemInfo);
if (target != null) currentTargets.add(target);
}
bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> events = new ArrayList<>();
ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
workspaceItems.addAll(dataModel.appWidgets);
for (ItemInfo item : workspaceItems) {
AppTarget target = getAppTargetFromInfo(context, item);
if (target != null && !isTrackedForPrediction(item)) continue;
events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
}
ArrayList<AppTarget> currentTargets = new ArrayList<>();
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
if (hotseatItems != null) {
for (ItemInfo itemInfo : hotseatItems.items) {
AppTarget target = getAppTargetFromInfo(context, itemInfo);
if (target != null) currentTargets.add(target);
}
});
}
bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
return bundle;
}
/**
* Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
* if item is not supported prediction
*/
public AppTarget getAppTargetFromInfo(ItemInfo info) {
public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) {
if (info == null) return null;
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
&& info instanceof LauncherAppWidgetInfo
@@ -107,17 +97,17 @@ public class HotseatPredictionModel extends PredictionModel {
shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
mContext.getPackageName(), info.user).build();
context.getPackageName(), info.user).build();
}
return null;
}
/**
* Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
* location using {@link ItemInfo}
*/
public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) {
public static AppTargetEvent wrapAppTargetWithLocation(
AppTarget target, int action, ItemInfo info) {
String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
@@ -19,8 +19,10 @@ import static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BA
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.GridBackupTable;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -29,29 +31,24 @@ import com.android.launcher3.provider.LauncherDbUtils;
* A helper class to manage migration revert restoration for hybrid hotseat
*/
public class HotseatRestoreHelper {
private final Launcher mLauncher;
HotseatRestoreHelper(Launcher context) {
mLauncher = context;
}
/**
* Creates a snapshot backup of Favorite table for future restoration use.
*/
public void createBackup() {
public static void createBackup(Context context) {
MODEL_EXECUTOR.execute(() -> {
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
LauncherSettings.Settings.call(
mLauncher.getContentResolver(),
context.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
GridBackupTable backupTable = new GridBackupTable(mLauncher,
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
GridBackupTable backupTable = new GridBackupTable(context,
transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
idp.numRows);
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
transaction.commit();
LauncherSettings.Settings.call(mLauncher.getContentResolver(),
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
}
});
@@ -60,23 +57,23 @@ public class HotseatRestoreHelper {
/**
* Finds and restores a previously saved snapshow of Favorites table
*/
public void restoreBackup() {
public static void restoreBackup(Context context) {
MODEL_EXECUTOR.execute(() -> {
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
LauncherSettings.Settings.call(
mLauncher.getContentResolver(),
context.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
return;
}
InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
GridBackupTable backupTable = new GridBackupTable(mLauncher,
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
GridBackupTable backupTable = new GridBackupTable(context,
transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
idp.numRows);
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
transaction.commit();
mLauncher.getModel().forceReload();
LauncherAppState.getInstance(context).getModel().forceReload();
}
});
}
@@ -15,8 +15,21 @@
*/
package com.android.launcher3.model;
import static android.app.prediction.AppTargetEvent.ACTION_DISMISS;
import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
import static android.app.prediction.AppTargetEvent.ACTION_PIN;
import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
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;
@@ -47,11 +60,12 @@ 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.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.ObjIntConsumer;
import java.util.function.Predicate;
/**
@@ -64,9 +78,11 @@ public class AppEventProducer implements StatsLogConsumer {
private final Context mContext;
private final Handler mMessageHandler;
private final Consumer<AppTargetEvent> mCallback;
private final ObjIntConsumer<AppTargetEvent> mCallback;
public AppEventProducer(Context context, Consumer<AppTargetEvent> callback) {
private LauncherAtom.ItemInfo mLastDragItem;
public AppEventProducer(Context context, ObjIntConsumer<AppTargetEvent> callback) {
mContext = context;
mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage);
mCallback = callback;
@@ -76,7 +92,7 @@ public class AppEventProducer implements StatsLogConsumer {
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_LAUNCH: {
mCallback.accept((AppTargetEvent) msg.obj);
mCallback.accept((AppTargetEvent) msg.obj, msg.arg1);
return true;
}
}
@@ -84,13 +100,18 @@ public class AppEventProducer implements StatsLogConsumer {
}
@AnyThread
private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) {
AppTarget target = toAppTarget(atomInfo);
private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId, int targetPredictor) {
sendEvent(toAppTarget(atomInfo), atomInfo, eventId, targetPredictor);
}
@AnyThread
private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
int targetPredictor) {
if (target != null) {
AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
.setLaunchLocation(getContainer(atomInfo))
.setLaunchLocation(getContainer(locationInfo))
.build();
Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
mMessageHandler.obtainMessage(MSG_LAUNCH, targetPredictor, 0, event).sendToTarget();
}
}
@@ -101,9 +122,42 @@ public class AppEventProducer implements StatsLogConsumer {
|| event == LAUNCHER_TASK_LAUNCH_TAP
|| event == LAUNCHER_QUICKSWITCH_RIGHT
|| event == LAUNCHER_QUICKSWITCH_LEFT) {
sendEvent(atomInfo, AppTargetEvent.ACTION_LAUNCH);
sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
sendEvent(atomInfo, AppTargetEvent.ACTION_DISMISS);
sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_ITEM_DRAG_STARTED) {
mLastDragItem = atomInfo;
} else if (event == LAUNCHER_ITEM_DROP_COMPLETED) {
if (mLastDragItem == null) {
return;
}
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
if (isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
mLastDragItem = null;
} else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(createTempFolderTarget(), atomInfo, ACTION_PIN,
CONTAINER_HOTSEAT_PREDICTION);
sendEvent(atomInfo, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_FOLDER_CONVERTED_TO_ICON) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(createTempFolderTarget(), atomInfo, ACTION_UNPIN,
CONTAINER_HOTSEAT_PREDICTION);
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_ITEM_DROPPED_ON_REMOVE) {
if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
}
}
@@ -152,10 +206,8 @@ public class AppEventProducer implements StatsLogConsumer {
}
break;
}
case FOLDER_ICON: {
id = "folder:" + SystemClock.uptimeMillis();
cn = new ComponentName(mContext.getPackageName(), "#folder");
}
case FOLDER_ICON:
return createTempFolderTarget();
}
if (id != null && cn != null) {
return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
@@ -165,6 +217,12 @@ public class AppEventProducer implements StatsLogConsumer {
return null;
}
private AppTarget createTempFolderTarget() {
return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
mContext.getPackageName(), Process.myUserHandle())
.build();
}
private String getContainer(LauncherAtom.ItemInfo info) {
ContainerInfo ci = info.getContainerInfo();
switch (ci.getContainerCase()) {
@@ -212,11 +270,26 @@ public class AppEventProducer implements StatsLogConsumer {
}
private static String getHotseatContainerString(HotseatContainer hc) {
return String.format(Locale.ENGLISH, "hotseat/%d", hc.getIndex());
return String.format(Locale.ENGLISH, "hotseat/%1$d/[%1$d,0]/[1,1]", hc.getIndex());
}
private static ComponentName parseNullable(String componentNameString) {
return TextUtils.isEmpty(componentNameString)
? null : ComponentName.unflattenFromString(componentNameString);
}
/**
* Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
*/
private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
ContainerInfo ci = info.getContainerInfo();
switch (ci.getContainerCase()) {
case HOTSEAT:
return true;
case WORKSPACE:
return ci.getWorkspace().getPageIndex() == 0;
default:
return false;
}
}
}
@@ -16,9 +16,11 @@
package com.android.launcher3.model;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
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.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -62,6 +64,8 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
private final PredictorState mAllAppsState =
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
private final PredictorState mHotseatState =
new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
@@ -81,13 +85,23 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
// TODO: Implement caching and preloading
super.loadItems(ums, pinnedShortcuts);
WorkspaceItemFactory factory =
WorkspaceItemFactory allAppsFactory =
new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns);
mAllAppsState.items.setItems(
mAllAppsState.storage.read(mApp.getContext(), factory, ums.allUsers::get));
mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get));
mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items);
WorkspaceItemFactory hotseatFactory =
new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numHotseatIcons);
mHotseatState.items.setItems(
mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items);
mActive = true;
}
@Override
public void workspaceLoadComplete() {
super.workspaceLoadComplete();
recreatePredictors();
}
@@ -111,6 +125,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
private void destroyPredictors() {
mAllAppsState.destroyPredictor();
mHotseatState.destroyPredictor();
}
@WorkerThread
@@ -125,18 +140,28 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
return;
}
int count = mIDP.numAllAppsColumns;
mAllAppsState.predictor = apm.createAppPredictionSession(
registerPredictor(mAllAppsState, apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("home")
.setPredictedTargetCount(count)
.build());
mAllAppsState.predictor.registerPredictionUpdates(
Executors.MODEL_EXECUTOR, t -> handleUpdate(mAllAppsState, t));
mAllAppsState.predictor.requestPredictionUpdate();
.setPredictedTargetCount(mIDP.numAllAppsColumns)
.build()));
// TODO: get bundle
registerPredictor(mHotseatState, apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("hotseat")
.setPredictedTargetCount(mIDP.numHotseatIcons)
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
.build()));
}
private void registerPredictor(PredictorState state, AppPredictor predictor) {
state.predictor = predictor;
state.predictor.registerPredictionUpdates(
Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t));
state.predictor.requestPredictionUpdate();
}
private void handleUpdate(PredictorState state, List<AppTarget> targets) {
if (state.setTargets(targets)) {
@@ -154,9 +179,10 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
}
}
private void onAppTargetEvent(AppTargetEvent event) {
if (mAllAppsState.predictor != null) {
mAllAppsState.predictor.notifyAppTargetEvent(event);
private void onAppTargetEvent(AppTargetEvent event, int client) {
PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
if (state.predictor != null) {
state.predictor.notifyAppTargetEvent(event);
}
}
@@ -33,8 +33,8 @@ import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -48,14 +48,11 @@ import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
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;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.testing.TestProtocol;
@@ -70,13 +67,14 @@ import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchContro
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -84,7 +82,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -98,14 +95,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
private FixedContainerItems mAllAppsPredictions;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.createPredictor();
}
}
private HotseatPredictionController mHotseatPredictionController;
@Override
protected void setupViews() {
@@ -143,6 +133,18 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
}
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
@Override
protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -178,22 +180,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
super.showAllAppsFromIntent(alreadyOnHome);
}
@Override
public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
super.folderCreatedFromItem(folder, itemInfo);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
}
}
@Override
public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
super.folderConvertedToItem(folder, itemInfo);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
}
}
@Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
if (mHotseatPredictionController != null) {
@@ -222,20 +208,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
}
@Override
public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
super.bindPredictedItems(appInfos, ranks);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.showCachedItems(appInfos, ranks);
}
}
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION
&& mHotseatPredictionController != null) {
mHotseatPredictionController.setPredictedItems(item);
}
}
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -30,7 +29,6 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.LayoutUtils;
@@ -177,8 +175,6 @@ public class OverviewState extends LauncherState {
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
newContainerTarget(ContainerType.OVERVIEW));
taskView.launchTask(true);
} else {
super.onBackPressed(launcher);
@@ -19,7 +19,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
@@ -221,7 +220,7 @@ public class NavBarToHomeTouchController implements TouchController,
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
}
if (mStartState == OVERVIEW) {
if (mStartState.overviewUi) {
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
.animateWithVelocity(velocity);
} else {
@@ -229,12 +228,12 @@ public class NavBarToHomeTouchController implements TouchController,
() -> onSwipeInteractionCompleted(mEndState));
}
if (mStartState != mEndState) {
logStateChange(mStartState.containerType, logAction);
// TODO: add to WW log
}
AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topOpenView != null) {
AbstractFloatingView.closeAllOpenViews(mLauncher);
logStateChange(topOpenView.getLogContainerType(), logAction);
// TODO: add to WW log
}
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
@@ -29,7 +29,6 @@ import android.view.ViewConfiguration;
import androidx.annotation.BinderThread;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.ActivityInitListener;
@@ -87,12 +86,6 @@ public class OverviewCommandHelper {
MAIN_EXECUTOR.execute(new HideRecentsCommand());
}
@BinderThread
public void onTip(int actionType, int viewType) {
MAIN_EXECUTOR.execute(() ->
UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
}
private class ShowRecentsCommand extends RecentsActivityCommand {
private final boolean mTriggeredFromAltTab;
@@ -22,7 +22,6 @@ import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -61,7 +60,6 @@ import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
@@ -141,7 +139,6 @@ public class TouchInteractionService extends Service implements PluginListener<O
}
@BinderThread
@Override
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
mOverviewCommandHelper.onOverviewToggle();
@@ -165,7 +162,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
@BinderThread
@Override
public void onTip(int actionType, int viewType) {
mOverviewCommandHelper.onTip(actionType, viewType);
// Please delete this method from the interface
}
@BinderThread
@@ -189,18 +186,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
@BinderThread
public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
if (mOverviewComponentObserver == null) {
return;
}
final BaseActivityInterface activityInterface =
mOverviewComponentObserver.getActivityInterface();
UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
isButton, gestureSwipeLeft, activityInterface.getContainerType());
if (completed && !isButton && shouldNotifyBackGesture()) {
UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
}
// Remove this method from the interface
}
@BinderThread
@@ -263,10 +249,10 @@ public class TouchInteractionService extends Service implements PluginListener<O
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
@@ -1,86 +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 android.util.Log;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.systemui.shared.system.MetricsLoggerCompat;
/**
* This class handles AOSP MetricsLogger function calls and logging around
* quickstep interactions.
*/
@SuppressWarnings("unused")
public class UserEventDispatcherExtension extends UserEventDispatcher {
public static final int ALL_APPS_PREDICTION_TIPS = 2;
private static final String TAG = "UserEventDispatcher";
public UserEventDispatcherExtension(Context context) { }
public void logStateChangeAction(int action, int dir, int downX, int downY,
int srcChildTargetType, int srcParentContainerType,
int dstContainerType, int pageIndex) {
new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
super.logStateChangeAction(action, dir, downX, downY, srcChildTargetType,
srcParentContainerType, dstContainerType, pageIndex);
}
public void logActionTip(int actionType, int viewType) {
LauncherLogProto.Action action = new LauncherLogProto.Action();
LauncherLogProto.Target target = new LauncherLogProto.Target();
switch(actionType) {
case VISIBLE:
action.type = LauncherLogProto.Action.Type.TIP;
target.type = LauncherLogProto.Target.Type.CONTAINER;
target.containerType = LauncherLogProto.ContainerType.TIP;
break;
case DISMISS:
action.type = LauncherLogProto.Action.Type.TOUCH;
action.touch = LauncherLogProto.Action.Touch.TAP;
target.type = LauncherLogProto.Target.Type.CONTROL;
target.controlType = CANCEL_TARGET;
break;
default:
Log.e(TAG, "Unexpected action type = " + actionType);
}
switch(viewType) {
case RECENTS_QUICK_SCRUB_ONBOARDING_TIP:
target.tipType = LauncherLogProto.TipType.QUICK_SCRUB_TEXT;
break;
case RECENTS_SWIPE_UP_ONBOARDING_TIP:
target.tipType = LauncherLogProto.TipType.SWIPE_UP_TEXT;
break;
default:
Log.e(TAG, "Unexpected viewType = " + viewType);
}
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
dispatchUserEvent(event, null);
}
}
@@ -31,11 +31,12 @@ import android.util.FloatProperty;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.views.RecentsView;
@@ -54,11 +55,12 @@ public class AnimatorControllerWithResistance {
*/
public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
private enum RecentsParams {
private enum RecentsResistanceParams {
FROM_APP(0.75f, 0.5f, 1f),
FROM_OVERVIEW(1f, 0.75f, 0.5f);
RecentsParams(float scaleStartResist, float scaleMaxResist, float translationFactor) {
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
float translationFactor) {
this.scaleStartResist = scaleStartResist;
this.scaleMaxResist = scaleMaxResist;
this.translationFactor = translationFactor;
@@ -139,9 +141,9 @@ public class AnimatorControllerWithResistance {
FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
FloatProperty<TRANSLATION> translationProperty) {
PendingAnimation resistAnim = createRecentsResistanceAnim(null, context,
recentsOrientedState, dp, scaleTarget, scaleProperty, translationTarget,
translationProperty, RecentsParams.FROM_APP);
RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
scaleProperty, translationTarget, translationProperty);
PendingAnimation resistAnim = createRecentsResistanceAnim(params);
AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
return new AnimatorControllerWithResistance(normalController, resistanceController);
@@ -152,31 +154,30 @@ public class AnimatorControllerWithResistance {
* when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
*/
public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
@Nullable PendingAnimation resistAnim, Context context,
RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
FloatProperty<TRANSLATION> translationProperty, RecentsParams params) {
RecentsParams<SCALE, TRANSLATION> params) {
Rect startRect = new Rect();
LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, startRect,
recentsOrientedState.getOrientationHandler());
PagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
orientationHandler);
long distanceToCover = startRect.bottom;
boolean isTwoButtonMode = SysUINavigationMode.getMode(context) == TWO_BUTTONS;
boolean isTwoButtonMode = SysUINavigationMode.getMode(params.context) == TWO_BUTTONS;
if (isTwoButtonMode) {
// We can only drag a small distance past overview, not to the top of the screen.
distanceToCover = (long)
((dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
}
if (resistAnim == null) {
resistAnim = new PendingAnimation(distanceToCover * 2);
((params.dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
}
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
: new PendingAnimation(distanceToCover * 2);
PointF pivot = new PointF();
float fullscreenScale = recentsOrientedState.getFullScreenScaleAndPivot(
startRect, dp, pivot);
float startScale = 1;
float prevScaleRate = (fullscreenScale - startScale) / (dp.heightPx - startRect.bottom);
float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
startRect, params.dp, pivot);
float prevScaleRate = (fullscreenScale - params.startScale)
/ (params.dp.heightPx - startRect.bottom);
// This is what the scale would be at the end of the drag if we didn't apply resistance.
float endScale = startScale - prevScaleRate * distanceToCover;
float endScale = params.startScale - prevScaleRate * distanceToCover;
final TimeInterpolator scaleInterpolator;
if (isTwoButtonMode) {
// We are bounded by the distance of the drag, so we don't need to apply resistance.
@@ -184,9 +185,10 @@ public class AnimatorControllerWithResistance {
} else {
// Create an interpolator that resists the scale so the scale doesn't get smaller than
// RECENTS_SCALE_MAX_RESIST.
float startResist = Utilities.getProgress(params.scaleStartResist , startScale,
endScale);
float maxResist = Utilities.getProgress(params.scaleMaxResist, startScale, endScale);
float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
params.startScale, endScale);
float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
params.startScale, endScale);
scaleInterpolator = t -> {
if (t < startResist) {
return t;
@@ -196,20 +198,22 @@ public class AnimatorControllerWithResistance {
return startResist + resistProgress * (maxResist - startResist);
};
}
resistAnim.addFloat(scaleTarget, scaleProperty, startScale, endScale,
resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
scaleInterpolator);
if (!isTwoButtonMode) {
// Compute where the task view would be based on the end scale, if we didn't translate.
RectF endRectF = new RectF(startRect);
Matrix temp = new Matrix();
temp.setScale(params.scaleMaxResist, params.scaleMaxResist, pivot.x, pivot.y);
temp.setScale(params.resistanceParams.scaleMaxResist,
params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
temp.mapRect(endRectF);
// Translate such that the task view touches the top of the screen when drag does.
float endTranslation = endRectF.top * recentsOrientedState.getOrientationHandler()
.getSecondaryTranslationDirectionFactor() * params.translationFactor;
resistAnim.addFloat(translationTarget, translationProperty, 0, endTranslation,
RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
float endTranslation = endRectF.top
* orientationHandler.getSecondaryTranslationDirectionFactor()
* params.resistanceParams.translationFactor;
resistAnim.addFloat(params.translationTarget, params.translationProperty,
params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
}
return resistAnim;
@@ -220,11 +224,66 @@ public class AnimatorControllerWithResistance {
* a RecentsView interaction that started from the overview state.
*/
public static PendingAnimation createRecentsResistanceFromOverviewAnim(
BaseDraggingActivity activity, @Nullable PendingAnimation resistanceAnim) {
RecentsView recentsView = activity.getOverviewPanel();
return createRecentsResistanceAnim(resistanceAnim, activity,
recentsView.getPagedViewOrientedState(), activity.getDeviceProfile(),
recentsView, RECENTS_SCALE_PROPERTY, recentsView, TASK_SECONDARY_TRANSLATION,
RecentsParams.FROM_OVERVIEW);
Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
RecentsView recentsView = launcher.getOverviewPanel();
RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
TASK_SECONDARY_TRANSLATION)
.setResistAnim(resistanceAnim)
.setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
.setStartScale(recentsView.getScaleX());
return createRecentsResistanceAnim(params);
}
/**
* Params to compute resistance when scaling/translating recents.
*/
private static class RecentsParams<SCALE, TRANSLATION> {
// These are all required and can't have default values, hence are final.
public final Context context;
public final RecentsOrientedState recentsOrientedState;
public final DeviceProfile dp;
public final SCALE scaleTarget;
public final FloatProperty<SCALE> scaleProperty;
public final TRANSLATION translationTarget;
public final FloatProperty<TRANSLATION> translationProperty;
// These are not required, or can have a default value that is generally correct.
@Nullable public PendingAnimation resistAnim = null;
public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP;
public float startScale = 1f;
public float startTranslation = 0f;
private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty,
TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) {
this.context = context;
this.recentsOrientedState = recentsOrientedState;
this.dp = dp;
this.scaleTarget = scaleTarget;
this.scaleProperty = scaleProperty;
this.translationTarget = translationTarget;
this.translationProperty = translationProperty;
}
private RecentsParams setResistAnim(PendingAnimation resistAnim) {
this.resistAnim = resistAnim;
return this;
}
private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
this.resistanceParams = resistanceParams;
return this;
}
private RecentsParams setStartScale(float startScale) {
this.startScale = startScale;
return this;
}
private RecentsParams setStartTranslation(float startTranslation) {
this.startTranslation = startTranslation;
return this;
}
}
}
@@ -22,6 +22,7 @@ import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
@@ -106,10 +107,16 @@ public class OverviewToHomeAnim {
// StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
? PLAY_ATOMIC_OVERVIEW_PEEK
: ANIM_ALL_COMPONENTS;
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, DEACCEL);
boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
.isLayoutNaturalToLauncher();
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
? DEACCEL : FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT);
if (!isLayoutNaturalToLauncher) {
config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL);
}
AnimatorSet stateAnim = stateManager.createAtomicAnimation(
startState, NORMAL, config);
stateAnim.addListener(new AnimationSuccessListener() {
@@ -26,13 +26,13 @@ import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import android.content.SharedPreferences;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.views.AllAppsEduView;
@@ -40,9 +40,9 @@ import com.android.quickstep.views.AllAppsEduView;
/**
* Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
*/
public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLauncher> {
public class QuickstepOnboardingPrefs extends OnboardingPrefs<QuickstepLauncher> {
public QuickstepOnboardingPrefs(BaseQuickstepLauncher launcher, SharedPreferences sharedPrefs) {
public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) {
super(launcher, sharedPrefs);
StateManager<LauncherState> stateManager = launcher.getStateManager();
@@ -104,11 +104,6 @@ public class AllAppsEduView extends AbstractFloatingView {
mLauncher.getDragLayer().removeView(this);
}
@Override
public void logActionCommand(int command) {
// TODO
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ALL_APPS_EDU) != 0;
@@ -110,8 +110,6 @@ import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DynamicResource;
@@ -142,7 +140,6 @@ import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.LauncherEventUtil;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -673,9 +670,6 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
public void onDigitalWellbeingToastShown() {
if (!mDwbToastShown) {
mDwbToastShown = true;
mActivity.getUserEventDispatcher().logActionTip(
LauncherEventUtil.VISIBLE,
LauncherLogProto.TipType.DWB_TOAST);
}
}
@@ -1461,8 +1455,6 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
if (taskView.getTask() != null) {
ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
endState.logAction, Direction.UP, index, compKey);
mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
@@ -2174,9 +2166,6 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
tv.launchTask(false, onLaunchResult, getHandler());
Task task = tv.getTask();
if (task != null) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
endState.logAction, Direction.DOWN, indexOfChild(tv),
TaskUtils.getLaunchComponentKeyForTask(task.key));
mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
}
@@ -132,11 +132,6 @@ public class TaskMenuView extends AbstractFloatingView {
}
}
@Override
public void logActionCommand(int command) {
// TODO
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -225,10 +225,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
} else {
launchTask(true /* animate */);
}
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
TaskUtils.getLaunchComponentKeyForTask(getTask().key));
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
});
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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.
-->
<com.android.launcher3.views.SearchSettingsRowView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TextHeadline"
android:id="@+id/section_title"
android:background="?android:attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:padding="4dp"
android:minHeight="48dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp">
<TextView
android:id="@+id/title"
style="@style/TextTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<TextView
android:id="@+id/description"
style="@style/TextTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
<TextView
android:id="@+id/breadcrumbs"
style="@style/TextTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:alpha=".7"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</com.android.launcher3.views.SearchSettingsRowView>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="4dp" />
-3
View File
@@ -35,9 +35,6 @@
<color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
<color name="all_apps_section_fill">#32c0c0c0</color>
<color name="all_apps_section_focused_item">#40c0c0c0</color>
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
-1
View File
@@ -69,7 +69,6 @@
<string name="app_launch_tracker_class" translatable="false"></string>
<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 -->
-6
View File
@@ -44,12 +44,6 @@
android:defaultValue="@bool/allow_rotation"
android:persistent="true" />
<SwitchPreference
android:key="pref_grid_options"
android:title="Enable grid options"
android:defaultValue="false"
android:persistent="true" />
<androidx.preference.PreferenceScreen
android:key="pref_developer_options"
android:persistent="false"
@@ -36,8 +36,6 @@ import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -129,8 +127,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public final void close(boolean animate) {
animate &= areAnimatorsEnabled();
if (mIsOpen) {
BaseActivity.fromContext(getContext()).getUserEventDispatcher()
.resetElapsedContainerMillis("container closed");
// Add to WW logging
}
handleClose(animate);
mIsOpen = false;
@@ -145,12 +142,6 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) { }
public abstract void logActionCommand(int command);
public int getLogContainerType() {
return ContainerType.DEFAULT_CONTAINERTYPE;
}
public final boolean isOpen() {
return mIsOpen;
}
@@ -159,7 +150,6 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
/** @return Whether the back is consumed. If false, Launcher will handle the back as well. */
public boolean onBackPressed() {
logActionCommand(Action.Command.BACK);
close(true);
return true;
}
@@ -536,11 +536,6 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
mDragLayer.removeView(this);
}
@Override
public void logActionCommand(int command) {
// TODO: Log this case.
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
@@ -193,7 +193,6 @@ public abstract class BaseDraggingActivity extends BaseActivity
getSystemService(LauncherApps.class).startMainActivity(
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
}
getUserEventDispatcher().logAppLaunch(v, intent, user);
if (item != null) {
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
logAppLaunch(item, instanceId);
+12 -5
View File
@@ -79,7 +79,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private static final int DISPLAY_FOLDER = 2;
private static final int DISPLAY_HERO_APP = 5;
private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -280,7 +280,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
applyDotState(info, false /* animate */);
}
public void applyFromPackageItemInfo(PackageItemInfo info) {
/**
* Apply label and tag using a generic {@link ItemInfoWithIcon}
*/
public void applyFromItemInfoWithIcon(ItemInfoWithIcon info) {
applyIconAndLabel(info);
// We don't need to check the info since it's not a WorkspaceItemInfo
super.setTag(info);
@@ -407,12 +410,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/**
* Draws the notification dot in the top right corner of the icon bounds.
*
* @param canvas The canvas to draw to.
*/
protected void drawDotIfNecessary(Canvas canvas) {
if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
getIconBounds(mDotParams.iconBounds);
Utilities.scaleRectAboutCenter(mDotParams.iconBounds, IconShape.getNormalizationScale());
Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
IconShape.getNormalizationScale());
final int scrollX = getScrollX();
final int scrollY = getScrollY();
canvas.translate(scrollX, scrollY);
@@ -507,6 +512,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/**
* Creates an animator to fade the text in or out.
*
* @param fadeIn Whether the text should fade in or fade out.
*/
public ObjectAnimator createTextAlphaAnimator(boolean fadeIn) {
@@ -663,7 +669,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
applyFromWorkspaceItem((WorkspaceItemInfo) info);
mActivity.invalidateParent(info);
} else if (info instanceof PackageItemInfo) {
applyFromPackageItemInfo((PackageItemInfo) info);
applyFromItemInfoWithIcon((PackageItemInfo) info);
}
mDisableRelayout = false;
@@ -761,7 +767,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
public SafeCloseable prepareDrawDragView() {
resetIconScale();
setForceHideDot(true);
return () -> { };
return () -> {
};
}
private void resetIconScale() {
+1 -2
View File
@@ -19,12 +19,11 @@ package com.android.launcher3;
import android.view.View;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
/**
* Interface defining an object that can originate a drag.
*/
public interface DragSource extends LogContainerProvider {
public interface DragSource {
/**
* A callback made back to the source after an item from this source has been dropped on a
-12
View File
@@ -113,18 +113,6 @@ public interface DropTarget {
return res;
}
/**
* This is used to determine if an object is dropped at a different location than it was
* dragged from
*/
public boolean isMoved() {
return dragInfo.cellX != originalDragInfo.cellX
|| dragInfo.cellY != originalDragInfo.cellY
|| dragInfo.screenId != originalDragInfo.screenId
|| dragInfo.container != originalDragInfo.container;
}
}
/**
+1 -15
View File
@@ -110,7 +110,6 @@ import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.IconCache;
@@ -911,7 +910,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
@CallSuper
protected void onDeferredResumed() {
logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
// Process any items that were added while Launcher was away.
ItemInstallQueue.INSTANCE.get(this)
@@ -929,6 +927,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
protected void handlePendingActivityRequest() { }
private void logStopAndResume(int command) {
if (mPendingExecutor != null) return;
int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
int containerType = mStateManager.getState().containerType;
@@ -1746,16 +1745,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
return newFolder;
}
/**
* Called when a workspace item is converted into a folder
*/
public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){}
/**
* Called when a folder is converted into a workspace item
*/
public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {}
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
@@ -2193,9 +2182,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
workspace.requestLayout();
}
@Override
public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
/**
* Add the views for a widget to the workspace.
*/
@@ -33,7 +33,6 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.PredictionModel;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
@@ -58,7 +57,6 @@ public class LauncherAppState {
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final PredictionModel mPredictionModel;
private SecureSettingsObserver mNotificationDotsObserver;
private InstallSessionTracker mInstallSessionTracker;
@@ -125,7 +123,6 @@ public class LauncherAppState {
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext));
mPredictionModel = PredictionModel.newInstance(mContext);
}
protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -182,10 +179,6 @@ public class LauncherAppState {
return mModel;
}
public PredictionModel getPredictionModel() {
return mPredictionModel;
}
public WidgetPreviewLoader getWidgetCache() {
return mWidgetCache;
}
@@ -218,6 +218,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
}
}
/**
* Called when the workspace items have drastically changed
*/
public void onWorkspaceUiChanged() {
MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
}
/**
* Called when the model is destroyed
*/
@@ -49,7 +49,6 @@ import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
import java.net.URISyntaxException;
import java.util.ArrayList;
/**
* Drop target which provides a secondary option for an item.
@@ -339,12 +338,6 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList
mDragObject = d;
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, Target child,
ArrayList<Target> parents) {
mOriginal.fillInLogContainerData(childInfo, child, parents);
}
@Override
public void onLauncherResume() {
// We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+1 -23
View File
@@ -28,7 +28,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -99,7 +98,6 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
@@ -1506,7 +1504,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
.showForIcon((BubbleTextView) child);
if (popupContainer != null) {
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
}
}
@@ -1706,7 +1703,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
fi.addItem(destInfo);
fi.addItem(sourceInfo);
}
mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
return true;
}
return false;
@@ -1723,7 +1719,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (dropOverView instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) dropOverView;
if (fi.acceptDrop(d.dragInfo)) {
mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
.log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
@@ -3272,24 +3268,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, Target child,
ArrayList<Target> parents) {
if (childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
getHotseat().fillInLogContainerData(childInfo, child, parents);
return;
} else if (childInfo.container >= 0) {
FolderIcon icon = (FolderIcon) getHomescreenIconByItemId(childInfo.container);
icon.getFolder().fillInLogContainerData(childInfo, child, parents);
return;
}
child.gridX = childInfo.cellX;
child.gridY = childInfo.cellY;
child.pageIndex = getCurrentPage();
parents.add(newContainerTarget(ContainerType.WORKSPACE));
}
/**
* Used as a workaround to ensure that the AppWidgetService receives the
* PACKAGE_ADDED broadcast before updating widgets.
@@ -17,7 +17,6 @@ package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -62,8 +61,6 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -71,8 +68,6 @@ import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
import java.util.ArrayList;
/**
* The all apps view container.
*/
@@ -354,13 +349,6 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
public void onDropCompleted(View target, DragObject d, boolean success) {
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, Target child,
ArrayList<Target> parents) {
parents.add(newContainerTarget(
getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
@@ -20,6 +20,7 @@ import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APP
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,18 +31,27 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.LiveData;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.slice.Slice;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -50,7 +60,9 @@ import java.util.List;
/**
* The grid view adapter of all the apps.
*/
public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
public class AllAppsGridAdapter extends
RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> implements
LifecycleOwner {
public static final String TAG = "AppsGridAdapter";
@@ -71,12 +83,18 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
public static final int DETAIL_ROW_WITH_BUTTON = 1 << 7;
public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7;
public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8;
public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 9;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
private final LifecycleRegistry mLifecycleRegistry;
/**
* ViewHolder for each icon.
*/
@@ -159,12 +177,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON
|| viewType == VIEW_TYPE_SEARCH_HERO_APP
|| viewType == DETAIL_ROW_WITH_BUTTON;
|| viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
|| viewType == VIEW_TYPE_SEARCH_SLICE
|| viewType == VIEW_TYPE_SEARCH_ROW;
}
}
/**
* Extension of AdapterItem that contains an extra payload specific to item
*
* @param <T> Play load Type
*/
public static class AdapterItemWithPayload<T> extends AdapterItem {
@@ -310,6 +331,12 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
mOnIconClickListener = launcher.getItemOnClickListener();
setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
} else {
mLifecycleRegistry = null;
}
}
public void setAppsPerRow(int appsPerRow) {
@@ -390,9 +417,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_SEARCH_HERO_APP:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_hero_app, parent, false));
case DETAIL_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_play_item, parent, false));
case VIEW_TYPE_SEARCH_ROW:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_settings_row, parent, false));
case VIEW_TYPE_SEARCH_SLICE:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_slice, parent, false));
default:
throw new RuntimeException("Unexpected view type");
}
@@ -421,9 +454,20 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
searchView.setVisibility(View.GONE);
}
break;
case VIEW_TYPE_SEARCH_SLICE:
SliceView sliceView = (SliceView) holder.itemView;
Uri uri = ((AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position))
.getPayload();
try {
LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher, uri);
liveData.observe(this::getLifecycle, sliceView);
} catch (Exception ignored) {
}
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
case DETAIL_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_HERO_APP:
case VIEW_TYPE_SEARCH_ROW:
PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
payloadResultView.applyAdapterInfo(
(AdapterItemWithPayload) mApps.getAdapterItems().get(position));
@@ -451,4 +495,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
return item.viewType;
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
@@ -22,6 +22,7 @@ import android.graphics.RectF;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
@@ -100,19 +101,23 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
* Handles grouping and drawing of items in the same all apps sections.
*/
public static class SectionDecorationHandler {
private static final int FILL_ALPHA = (int) (.3f * 255);
private static final int FOCUS_ALPHA = (int) (.8f * 255);
protected RectF mBounds = new RectF();
private final boolean mIsFullWidth;
private final float mRadius;
private final int mFocusColor;
private final int mFillcolor;
protected int mFocusColor;
protected int mFillcolor;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public SectionDecorationHandler(Context context, boolean isFullWidth) {
mIsFullWidth = isFullWidth;
mFillcolor = context.getColor(R.color.all_apps_section_fill);
mFocusColor = context.getColor(R.color.all_apps_section_focused_item);
int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
mFocusColor = ColorUtils.setAlphaComponent(endScrim, FOCUS_ALPHA);
mRadius = Themes.getDialogCornerRadius(context);
}
@@ -115,11 +115,6 @@ public class DiscoveryBounce extends AbstractFloatingView {
}
}
@Override
public void logActionCommand(int command) {
// Since this is on-boarding popup, it is not a user controlled action.
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_DISCOVERY_BOUNCE) != 0;
@@ -139,8 +139,8 @@ public class AppsSearchContainerLayout extends ExtendedEditText
mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
new DefaultAppSearchAlgorithm(LauncherAppState.getInstance(mLauncher)), this,
mLauncher, this, this);
new DefaultAppSearchAlgorithm(mLauncher, LauncherAppState.getInstance(mLauncher)),
this, mLauncher, this, this);
}
@Override
@@ -15,26 +15,15 @@
*/
package com.android.launcher3.allapps.search;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupPopulator;
import com.android.launcher3.shortcuts.ShortcutRequest;
import java.util.ArrayList;
import java.util.List;
@@ -46,63 +35,28 @@ import java.util.function.Consumer;
public class AppsSearchPipeline implements SearchPipeline {
private static final int MAX_RESULTS_COUNT = 5;
private static final int MAX_HERO_SECTION_COUNT = 2;
private static final int MAX_SHORTCUTS_COUNT = 2;
private final SearchSectionInfo mSearchSectionInfo;
private final LauncherAppState mLauncherAppState;
private final boolean mHeroSectionSupported;
public AppsSearchPipeline(LauncherAppState launcherAppState) {
this(launcherAppState, true);
}
public AppsSearchPipeline(LauncherAppState launcherAppState, boolean supportsHeroView) {
public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
mLauncherAppState = launcherAppState;
mSearchSectionInfo = new SearchSectionInfo();
mSearchSectionInfo.setDecorationHandler(
new SectionDecorationHandler(launcherAppState.getContext(), true));
mHeroSectionSupported = supportsHeroView;
new SectionDecorationHandler(context, true));
}
@Override
@WorkerThread
public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
List<AppInfo> matchingResults = getTitleMatchResult(apps.data, query);
if (mHeroSectionSupported && matchingResults.size() <= MAX_HERO_SECTION_COUNT) {
callback.accept(getHeroAdapterItems(app.getContext(), matchingResults));
} else {
callback.accept(getAdapterItems(matchingResults));
}
callback.accept(getAdapterItems(matchingResults));
}
});
}
/**
* Returns MAX_SHORTCUTS_COUNT shortcuts from local cache
* TODO: Shortcuts should be ranked based on relevancy
*/
private ArrayList<WorkspaceItemInfo> getShortcutInfos(Context context, AppInfo appInfo) {
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, appInfo.user)
.withContainer(appInfo.getTargetComponent())
.query(ShortcutRequest.PUBLISHED);
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, null);
IconCache cache = LauncherAppState.getInstance(context).getIconCache();
ArrayList<WorkspaceItemInfo> shortcutItems = new ArrayList<>();
for (int i = 0; i < shortcuts.size() && i < MAX_SHORTCUTS_COUNT; i++) {
final ShortcutInfo shortcut = shortcuts.get(i);
final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context);
cache.getUnbadgedShortcutIcon(si, shortcut);
si.rank = i;
si.container = CONTAINER_SHORTCUTS;
shortcutItems.add(si);
}
return shortcutItems;
}
/**
* Filters {@link AppInfo}s matching specified query
*/
@@ -121,21 +75,6 @@ public class AppsSearchPipeline implements SearchPipeline {
return result;
}
private ArrayList<AdapterItem> getHeroAdapterItems(Context context, List<AppInfo> apps) {
ArrayList<AdapterItem> adapterItems = new ArrayList<>();
for (int i = 0; i < apps.size(); i++) {
//hero app
AppInfo appInfo = apps.get(i);
ArrayList<WorkspaceItemInfo> shortcuts = getShortcutInfos(context, appInfo);
AdapterItem adapterItem = new AllAppsGridAdapter.AdapterItemWithPayload(shortcuts,
VIEW_TYPE_SEARCH_HERO_APP);
adapterItem.appInfo = appInfo;
adapterItem.searchSectionInfo = mSearchSectionInfo;
adapterItems.add(adapterItem);
}
return adapterItems;
}
private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
ArrayList<AdapterItem> items = new ArrayList<>();
for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps.search;
import android.content.Context;
import android.os.Handler;
import com.android.launcher3.LauncherAppState;
@@ -30,9 +31,9 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
protected final Handler mResultHandler;
private final AppsSearchPipeline mAppsSearchPipeline;
public DefaultAppSearchAlgorithm(LauncherAppState launcherAppState) {
public DefaultAppSearchAlgorithm(Context context, LauncherAppState launcherAppState) {
mResultHandler = new Handler();
mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState, false);
mAppsSearchPipeline = new AppsSearchPipeline(context, launcherAppState);
}
@Override
@@ -16,7 +16,6 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
@@ -33,15 +32,11 @@ import com.android.launcher3.DragSource;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingItemDragHelper;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import java.util.ArrayList;
/**
* {@link DragSource} for handling drop from a different window. This object is initialized
* in the source window and is passed on to the Launcher activity as an Intent extra.
@@ -106,12 +101,6 @@ public class PinItemDragListener extends BaseItemDragListener {
return dragHelper;
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> parents) {
parents.add(newContainerTarget(LauncherLogProto.ContainerType.PINITEM));
}
@Override
protected void postCleanup() {
super.postCleanup();
+3 -34
View File
@@ -19,11 +19,11 @@ package com.android.launcher3.folder;
import static android.text.TextUtils.isEmpty;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
@@ -1183,7 +1183,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
newIcon.requestFocus();
}
if (finalItem != null) {
mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
mStatsLogManager.logger().withItemInfo(finalItem)
.log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
}
}
}
@@ -1481,27 +1482,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
outRect.right += mScrollAreaOffset;
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> targets) {
child.gridX = childInfo.cellX;
child.gridY = childInfo.cellY;
child.pageIndex = mContent.getCurrentPage();
LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.FOLDER);
target.pageIndex = mInfo.screenId;
target.gridX = mInfo.cellX;
target.gridY = mInfo.cellY;
targets.add(target);
// continue to parent
if (mInfo.container == CONTAINER_HOTSEAT) {
mLauncher.getHotseat().fillInLogContainerData(mInfo, target, targets);
} else {
mLauncher.getWorkspace().fillInLogContainerData(mInfo, target, targets);
}
}
private class OnScrollHintListener implements OnAlarmListener {
private final DragObject mDragObject;
@@ -1589,17 +1569,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
return getOpenView(launcher, TYPE_FOLDER);
}
@Override
public void logActionCommand(int command) {
mLauncher.getUserEventDispatcher().logActionCommand(
command, getFolderIcon(), getLogContainerType());
}
@Override
public int getLogContainerType() {
return LauncherLogProto.ContainerType.FOLDER;
}
/**
* Navigation bar back key or hardware input back key has been issued.
*/
@@ -19,6 +19,7 @@ import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -72,12 +73,12 @@ import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
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;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -95,6 +96,7 @@ import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -467,12 +469,14 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper
}
IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
mIdp.numHotseatIcons);
int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size());
List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
int count = Math.min(ranks.size(), predictions.size());
for (int i = 0; i < count; i++) {
AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i);
int rank = ranks.get(i);
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo);
itemInfo.container = Favorites.CONTAINER_HOTSEAT_PREDICTION;
WorkspaceItemInfo itemInfo =
new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i));
itemInfo.container = CONTAINER_HOTSEAT_PREDICTION;
itemInfo.rank = rank;
itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
@@ -569,8 +573,7 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper
return null;
}
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel, null);
return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null);
}
}
@@ -594,11 +597,10 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper
}
@Override
public WorkspaceResult call() throws Exception {
public WorkspaceResult call() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
mBgDataModel.cachedPredictedItems, null, mWidgetProvidersMap);
return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap);
}
}
@@ -618,17 +620,16 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper
private static class WorkspaceResult {
private final ArrayList<ItemInfo> mWorkspaceItems;
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
private final ArrayList<AppInfo> mCachedPredictedItems;
private final FixedContainerItems mHotseatPredictions;
private final WidgetsModel mWidgetsModel;
private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
ArrayList<LauncherAppWidgetInfo> appWidgets,
ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel,
private WorkspaceResult(BgDataModel dataModel,
WidgetsModel widgetsModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
mWorkspaceItems = workspaceItems;
mAppWidgets = appWidgets;
mCachedPredictedItems = cachedPredictedItems;
mWorkspaceItems = dataModel.workspaceItems;
mAppWidgets = dataModel.appWidgets;
mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
mWidgetsModel = widgetsModel;
mWidgetProvidersMap = widgetProviderInfoMap;
}
@@ -316,7 +316,13 @@ public class StatsLogManager implements ResourceBasedOverride {
LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625),
@UiEvent(doc = "User tapped on image content in Overview Select mode.")
LAUNCHER_SELECT_MODE_IMAGE(627);
LAUNCHER_SELECT_MODE_IMAGE(627),
@UiEvent(doc = "A folder was replaced by a single item")
LAUNCHER_FOLDER_CONVERTED_TO_ICON(628),
@UiEvent(doc = "A hotseat prediction item was pinned")
LAUNCHER_HOTSEAT_PREDICTION_PINNED(629);
// ADD MORE
@@ -19,26 +19,21 @@ package com.android.launcher3.logging;
import static com.android.launcher3.logging.LoggerUtils.newAction;
import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newControlTarget;
import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.logging.LoggerUtils.newTarget;
import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
import static java.util.Optional.ofNullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
@@ -54,7 +49,6 @@ import com.android.launcher3.userevent.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.LogConfig;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -101,7 +95,7 @@ public class UserEventDispatcher implements ResourceBasedOverride {
*
* @return whether container data was added.
*/
public boolean fillLogContainer(@Nullable View v, Target child,
private boolean fillLogContainer(@Nullable View v, Target child,
@Nullable ArrayList<Target> targets) {
LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
@@ -125,55 +119,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
private boolean mAppOrTaskLaunch;
private boolean mPreviousHomeGesture;
// APP_ICON SHORTCUT WIDGET
// --------------------------------------------------------------
// packageNameHash required optional required
// componentNameHash required required
// intentHash required
// --------------------------------------------------------------
@Deprecated
public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
Target itemTarget = newItemTarget(v, mInstantAppResolver);
Action action = newTouchAction(Action.Touch.TAP);
ArrayList<Target> targets = makeTargetsList(itemTarget);
if (fillLogContainer(v, itemTarget, targets)) {
onFillInLogContainerData((ItemInfo) v.getTag(), itemTarget, targets);
fillIntentInfo(itemTarget, intent, userHandle);
}
LauncherEvent event = newLauncherEvent(action, targets);
dispatchUserEvent(event, intent);
mAppOrTaskLaunch = true;
}
/**
* Placeholder method.
*/
public void logActionTip(int actionType, int viewType) {
}
@Deprecated
public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
ComponentKey componentKey) {
LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
newTarget(Target.Type.ITEM));
if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
// Direction DOWN means the task was launched, UP means it was dismissed.
event.action.dir = direction;
}
event.srcTarget[0].itemType = ItemType.TASK;
event.srcTarget[0].pageIndex = taskIndex;
fillComponentInfo(event.srcTarget[0], componentKey.componentName);
dispatchUserEvent(event, null);
mAppOrTaskLaunch = true;
}
protected void fillIntentInfo(Target target, Intent intent, @Nullable UserHandle userHandle) {
target.intentHash = intent.hashCode();
target.isWorkApp = userHandle != null && !userHandle.equals(Process.myUserHandle());
fillComponentInfo(target, intent.getComponent());
}
private void fillComponentInfo(Target target, ComponentName cn) {
if (cn != null) {
target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
@@ -181,22 +126,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
}
}
public void logNotificationLaunch(View v, PendingIntent intent) {
LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
Target itemTarget = newItemTarget(v, mInstantAppResolver);
ArrayList<Target> targets = makeTargetsList(itemTarget);
if (fillLogContainer(v, itemTarget, targets)) {
itemTarget.packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
}
dispatchUserEvent(event, null);
}
public void logActionCommand(int command, Target srcTarget) {
logActionCommand(command, srcTarget, null);
}
public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
logActionCommand(command, newContainerTarget(srcContainerType),
dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
@@ -227,25 +156,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
dispatchUserEvent(event, null);
}
/**
* TODO: Make this function work when a container view is passed as the 2nd param.
*/
public void logActionCommand(int command, View itemView, int srcContainerType) {
LauncherEvent event = newLauncherEvent(newCommandAction(command),
newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
Target itemTarget = newItemTarget(itemView, mInstantAppResolver);
ArrayList<Target> targets = makeTargetsList(itemTarget);
if (fillLogContainer(itemView, itemTarget, targets)) {
// TODO: Remove the following two lines once fillInLogContainerData can take in a
// container view.
itemTarget.type = Target.Type.CONTAINER;
itemTarget.containerType = srcContainerType;
}
dispatchUserEvent(event, null);
}
public void logActionOnControl(int action, int controlType) {
logActionOnControl(action, controlType, null);
}
@@ -332,7 +242,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
event.srcTarget[0].spanX = downX;
event.srcTarget[0].spanY = downY;
dispatchUserEvent(event, null);
resetElapsedContainerMillis("state changed");
}
public void logActionOnItem(int action, int dir, int itemType) {
@@ -386,7 +295,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
ArrayList<Target> targets = makeTargetsList(child);
fillLogContainer(icon, child, targets);
dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
resetElapsedContainerMillis("deep shortcut open");
}
public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
@@ -397,7 +305,7 @@ public class UserEventDispatcher implements ResourceBasedOverride {
Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
ArrayList<Target> destTargets = makeTargetsList(destChild);
dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
//dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
if (dropTargetAsView instanceof LogContainerProvider) {
((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
destChild, destTargets);
@@ -414,35 +322,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
dispatchUserEvent(event, null);
}
public void logActionBack(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft, int containerType) {
int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
Action action = newCommandAction(actionTouch);
action.command = Action.Command.BACK;
action.dir = isButton ? Action.Direction.NONE :
gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
ControlType.BACK_GESTURE);
target.spanX = downX;
target.spanY = downY;
target.cardinality = completed ? 1 : 0;
LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType));
dispatchUserEvent(event, null);
}
/**
* Currently logs following containers: workspace, allapps, widget tray.
*/
public final void resetElapsedContainerMillis(String reason) {
mElapsedContainerMillis = SystemClock.uptimeMillis();
if (!IS_VERBOSE) {
return;
}
Log.d(TAG, "resetElapsedContainerMillis reason=" + reason);
}
public final void startSession() {
mSessionStarted = true;
mElapsedSessionMillis = SystemClock.uptimeMillis();
@@ -305,7 +305,7 @@ public class AllAppsList {
*
* @return the corresponding AppInfo or null
*/
private @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
public @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
@NonNull UserHandle user) {
for (AppInfo info: data) {
if (componentName.equals(info.componentName) && user.equals(info.user)) {
@@ -17,7 +17,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import android.util.Log;
@@ -206,9 +205,6 @@ public abstract class BaseLoaderResults {
mExtraItems.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
// Locate available spots for prediction using currentWorkspaceItems
IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
bindPredictedItems(gaps, mainExecutor);
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
@@ -261,11 +257,6 @@ public abstract class BaseLoaderResults {
}
}
private void bindPredictedItems(IntArray ranks, final Executor executor) {
ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems);
executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor);
}
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
@@ -102,11 +102,6 @@ public class BgDataModel {
*/
public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
/**
* List of all cached predicted items visible on home screen
*/
public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
/**
* Maps all launcher activities to counts of their shortcuts.
*/
@@ -470,10 +465,5 @@ public class BgDataModel {
default void bindExtraContainerItems(FixedContainerItems item) { }
void bindAllApplications(AppInfo[] apps, int flags);
/**
* Binds predicted appInfos at at available prediction slots.
*/
void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
}
}
@@ -17,25 +17,30 @@ package com.android.launcher3.model;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
import android.util.Log;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Helper class to send broadcasts to package installers that have:
@@ -61,26 +66,10 @@ public class FirstScreenBroadcast {
private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
private final MultiHashMap<String, String> mPackagesForInstaller;
private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage;
public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
}
/**
* @return Map where the key is the package name of the installer, and the value is a list
* of packages with active sessions for that installer.
*/
private MultiHashMap<String, String> getPackagesForInstaller(
HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
MultiHashMap<String, String> packagesForInstaller = new MultiHashMap<>();
for (Map.Entry<PackageUserKey, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
if (myUserHandle().equals(entry.getKey().mUser)) {
packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
entry.getKey().mPackageName);
}
}
return packagesForInstaller;
mSessionInfoForPackage = sessionInfoForPackage;
}
/**
@@ -88,9 +77,15 @@ public class FirstScreenBroadcast {
* first screen.
*/
public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
for (Map.Entry<String, ArrayList<String>> entry : mPackagesForInstaller.entrySet()) {
sendBroadcastToInstaller(context, entry.getKey(), entry.getValue(), firstScreenItems);
}
UserHandle myUser = myUserHandle();
mSessionInfoForPackage
.values()
.stream()
.filter(info -> myUser.equals(getUserHandle(info)))
.collect(groupingBy(SessionInfo::getInstallerPackageName,
mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
.forEach((installer, packages) ->
sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
}
/**
@@ -99,7 +94,7 @@ public class FirstScreenBroadcast {
* @param firstScreenItems List of items on the first screen.
*/
private void sendBroadcastToInstaller(Context context, String installerPackageName,
List<String> packages, List<ItemInfo> firstScreenItems) {
Set<String> packages, List<ItemInfo> firstScreenItems) {
Set<String> folderItems = new HashSet<>();
Set<String> workspaceItems = new HashSet<>();
Set<String> hotseatItems = new HashSet<>();
@@ -48,8 +48,6 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.TimingLogger;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
@@ -83,7 +81,6 @@ import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.TraceHelper;
@@ -92,8 +89,10 @@ import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
/**
@@ -190,13 +189,13 @@ public class LoaderTask implements Runnable {
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
loadCachedPredictions();
logger.addSplit("loadWorkspace");
verifyNotStopped();
mResults.bindWorkspace();
logger.addSplit("bindWorkspace");
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
logger.addSplit("sendFirstScreenActiveInstallsBroadcast");
@@ -304,7 +303,7 @@ public class LoaderTask implements Runnable {
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
final Set<PackageUserKey> pendingPackages = new HashSet<>();
boolean clearDb = false;
try {
@@ -485,7 +484,7 @@ public class LoaderTask implements Runnable {
// SdCard is not ready yet. Package might get available,
// once it is ready.
Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
pendingPackages.addToList(c.user, targetPkg);
pendingPackages.add(new PackageUserKey(targetPkg, c.user));
// Add the icon on the workspace anyway.
allowMissingTarget = true;
} else {
@@ -839,24 +838,6 @@ public class LoaderTask implements Runnable {
}
}
@WorkerThread
private void loadCachedPredictions() {
synchronized (mBgDataModel) {
List<ComponentKey> componentKeys =
mApp.getPredictionModel().getPredictionComponentKeys();
List<LauncherActivityInfo> l;
mBgDataModel.cachedPredictedItems.clear();
for (ComponentKey key : componentKeys) {
l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
if (l.size() == 0) continue;
AppInfo info = new AppInfo(l.get(0), key.user,
mUserManagerState.isUserQuiet(key.user));
mBgDataModel.cachedPredictedItems.add(info);
mIconCache.getTitleAndIcon(info, false);
}
}
}
private void sanitizeData() {
Context context = mApp.getContext();
if (mItemsDeleted) {
@@ -900,14 +881,6 @@ public class LoaderTask implements Runnable {
PackageInstallInfo.fromInstallingState(info));
}
}
for (AppInfo item : mBgDataModel.cachedPredictedItems) {
List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
item.componentName.getPackageName(), item.user);
for (LauncherActivityInfo info : l) {
boolean quietMode = mUserManagerState.isUserQuiet(item.user);
mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
}
}
mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
mUserManagerState.isAnyProfileQuietModeEnabled());
@@ -71,6 +71,12 @@ public class ModelDelegate implements ResourceBasedOverride {
@WorkerThread
public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
/**
* Called during loader after workspace loading is complete
*/
@WorkerThread
public void workspaceLoadComplete() { }
/**
* Called when the delegate is no loner needed
*/
@@ -36,8 +36,8 @@ import com.android.launcher3.util.IntSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
/**
@@ -56,13 +56,7 @@ public class ModelUtils {
ArrayList<T> currentScreenItems,
ArrayList<T> otherScreenItems) {
// Purge any null ItemInfos
Iterator<T> iter = allWorkspaceItems.iterator();
while (iter.hasNext()) {
ItemInfo i = iter.next();
if (i == null) {
iter.remove();
}
}
allWorkspaceItems.removeIf(Objects::isNull);
// Order the set of items by their containers first, this allows use to walk through the
// list sequentially, build up a list of containers that are in the specified screen,
// as well as all items in those containers.
@@ -1,136 +0,0 @@
/*
* 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.Executors.MODEL_EXECUTOR;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.UserHandle;
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
import java.util.ArrayList;
import java.util.List;
/**
* Model Helper for app predictions
*/
public class PredictionModel implements ResourceBasedOverride {
private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
private static final int MAX_CACHE_ITEMS = 5;
protected Context mContext;
private SharedPreferences mDevicePrefs;
private UserCache mUserCache;
/**
* Retrieve instance of this object that can be overridden in runtime based on the build
* variant of the application.
*/
public static PredictionModel newInstance(Context context) {
PredictionModel model = Overrides.getObject(PredictionModel.class, context,
R.string.prediction_model_class);
model.init(context);
return model;
}
protected void init(Context context) {
mContext = context;
mDevicePrefs = Utilities.getDevicePrefs(mContext);
mUserCache = UserCache.INSTANCE.get(mContext);
}
/**
* Formats and stores a list of component key in device preferences.
*/
@AnyThread
public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState appState = LauncherAppState.getInstance(mContext);
StringBuilder builder = new StringBuilder();
int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
for (int i = 0; i < count; i++) {
builder.append(serializeComponentKeyToString(componentKeys.get(i)));
builder.append("\n");
}
if (componentKeys.isEmpty() /* should invalidate loader items */) {
appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel model, AllAppsList apps) {
model.cachedPredictedItems.clear();
}
});
}
mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
});
}
/**
* parses and returns ComponentKeys saved by
* {@link PredictionModel#cachePredictionComponentKeys(List)}
*/
@WorkerThread
public List<ComponentKey> getPredictionComponentKeys() {
Preconditions.assertWorkerThread();
ArrayList<ComponentKey> items = new ArrayList<>();
String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
for (String line : cachedBlob.split("\n")) {
ComponentKey key = getComponentKeyFromSerializedString(line);
if (key != null) {
items.add(key);
}
}
return items;
}
private String serializeComponentKeyToString(ComponentKey componentKey) {
long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
}
private ComponentKey getComponentKeyFromSerializedString(String str) {
int sep = str.indexOf('#');
if (sep < 0 || (sep + 1) >= str.length()) {
return null;
}
ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
if (componentName == null) {
return null;
}
try {
long serialNumber = Long.parseLong(str.substring(sep + 1));
UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
} catch (NumberFormatException ex) {
return null;
}
}
}
@@ -24,12 +24,11 @@ import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
/**
* Helper class to re-query app status when SD-card becomes available.
@@ -42,10 +41,9 @@ public class SdCardAvailableReceiver extends BroadcastReceiver {
private final LauncherModel mModel;
private final Context mContext;
private final MultiHashMap<UserHandle, String> mPackages;
private final Set<PackageUserKey> mPackages;
public SdCardAvailableReceiver(LauncherAppState app,
MultiHashMap<UserHandle, String> packages) {
public SdCardAvailableReceiver(LauncherAppState app, Set<PackageUserKey> packages) {
mModel = app.getModel();
mContext = app.getContext();
mPackages = packages;
@@ -55,19 +53,17 @@ public class SdCardAvailableReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
UserHandle user = entry.getKey();
for (PackageUserKey puk : mPackages) {
UserHandle user = puk.mUser;
final ArrayList<String> packagesRemoved = new ArrayList<>();
final ArrayList<String> packagesUnavailable = new ArrayList<>();
for (String pkg : new HashSet<>(entry.getValue())) {
if (!launcherApps.isPackageEnabled(pkg, user)) {
if (pmHelper.isAppOnSdcard(pkg, user)) {
packagesUnavailable.add(pkg);
} else {
packagesRemoved.add(pkg);
}
if (!launcherApps.isPackageEnabled(puk.mPackageName, user)) {
if (pmHelper.isAppOnSdcard(puk.mPackageName, user)) {
packagesUnavailable.add(puk.mPackageName);
} else {
packagesRemoved.add(puk.mPackageName);
}
}
if (!packagesRemoved.isEmpty()) {
@@ -25,11 +25,12 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Handles changes due to shortcut manager updates (deep shortcut changes)
@@ -53,54 +54,53 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask {
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
final Context context = app.getContext();
// Find WorkspaceItemInfo's that have changed on the workspace.
HashSet<ShortcutKey> removedKeys = new HashSet<>();
MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
HashSet<String> allIds = new HashSet<>();
ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& mPackageName.equals(si.getIntent().getPackage())) {
keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
allIds.add(si.getDeepShortcutId());
matchingWorkspaceItems.add(si);
}
});
}
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
if (!keyToShortcutInfo.isEmpty()) {
if (!matchingWorkspaceItems.isEmpty()) {
// Update the workspace to reflect the changes to updated shortcuts residing on it.
List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
.map(WorkspaceItemInfo::getDeepShortcutId)
.distinct()
.collect(Collectors.toList());
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
.forPackage(mPackageName, new ArrayList<>(allIds))
.forPackage(mPackageName, allLauncherKnownIds)
.query(ShortcutRequest.ALL);
Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
for (ShortcutInfo fullDetails : shortcuts) {
ShortcutKey key = ShortcutKey.fromInfo(fullDetails);
List<WorkspaceItemInfo> workspaceItemInfos = keyToShortcutInfo.remove(key);
if (!fullDetails.isPinned()) {
// The shortcut was previously pinned but is no longer, so remove it from
// the workspace and our pinned shortcut counts.
// Note that we put this check here, after querying for full details,
// because there's a possible race condition between pinning and
// receiving this callback.
removedKeys.add(key);
continue;
}
for (final WorkspaceItemInfo workspaceItemInfo : workspaceItemInfos) {
workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
updatedWorkspaceItemInfos.add(workspaceItemInfo);
}
String sid = fullDetails.getId();
nonPinnedIds.remove(sid);
matchingWorkspaceItems
.stream()
.filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
.forEach(workspaceItemInfo -> {
workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
updatedWorkspaceItemInfos.add(workspaceItemInfo);
});
}
}
// If there are still entries in keyToShortcutInfo, that means that
// the corresponding shortcuts weren't passed in onShortcutsChanged(). This
// means they were cleared, so we remove and unpin them now.
removedKeys.addAll(keyToShortcutInfo.keySet());
bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!keyToShortcutInfo.isEmpty()) {
deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys));
bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!nonPinnedIds.isEmpty()) {
deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
nonPinnedIds.stream()
.map(id -> new ShortcutKey(mPackageName, mUser, id))
.collect(Collectors.toSet())));
}
}
if (mUpdateIdMap) {
@@ -106,7 +106,6 @@ public class NotificationInfo implements View.OnClickListener {
view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
try {
intent.send(null, 0, null, null, null, null, activityOptions);
launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
launcher.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
} catch (PendingIntent.CanceledException e) {
@@ -20,12 +20,9 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCU
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.AnimatorSet;
@@ -147,17 +144,6 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
return (type & TYPE_ACTION_POPUP) != 0;
}
@Override
public void logActionCommand(int command) {
mLauncher.getUserEventDispatcher().logActionCommand(
command, mOriginalIcon, getLogContainerType());
}
@Override
public int getLogContainerType() {
return ContainerType.DEEPSHORTCUTS;
}
public OnClickListener getItemClickListener() {
return (view) -> {
mLauncher.getItemOnClickListener().onClick(view);
@@ -495,18 +481,6 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
}
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, Target child,
ArrayList<Target> parents) {
if (childInfo == NOTIFICATION_ITEM_INFO) {
child.itemType = ItemType.NOTIFICATION;
} else {
child.itemType = ItemType.DEEPSHORTCUT;
child.rank = childInfo.rank;
}
parents.add(newContainerTarget(ContainerType.DEEPSHORTCUTS));
}
@Override
protected void onCreateCloseAnimation(AnimatorSet anim) {
// Animate original icon's text back in.
@@ -38,7 +38,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -202,20 +201,11 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
}
public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
for (WidgetListRowEntry entry : mAllWidgets) {
if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
// Remove widgets not associated with the correct user.
Iterator<WidgetItem> iterator = widgets.iterator();
while (iterator.hasNext()) {
if (!iterator.next().user.equals(packageUserKey.mUser)) {
iterator.remove();
}
}
return widgets.isEmpty() ? null : widgets;
}
}
return null;
return mAllWidgets.stream()
.filter(row -> row.pkgItem.packageName.equals(packageUserKey.mPackageName))
.flatMap(row -> row.widgets.stream())
.filter(widget -> packageUserKey.mUser.equals(widget.user))
.collect(Collectors.toList());
}
/**
@@ -99,7 +99,7 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
final List<WidgetItem> widgets =
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
if (widgets == null) {
if (widgets.isEmpty()) {
return null;
}
return new Widgets(launcher, itemInfo);
@@ -196,9 +196,6 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity
@Override
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
@Override
public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
@Override
public void bindScreens(IntArray orderedScreenIds) { }
@@ -27,6 +27,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import java.util.HashSet;
import java.util.Set;
/**
* A utility class to check for {@link ItemInfo}
@@ -99,7 +100,7 @@ public interface ItemInfoMatcher {
return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
}
static ItemInfoMatcher ofShortcutKeys(HashSet<ShortcutKey> keys) {
static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
return (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
keys.contains(ShortcutKey.fromItemInfo(info));
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2016 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.util;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A utility map from keys to an ArrayList of values.
*/
public class MultiHashMap<K, V> extends HashMap<K, ArrayList<V>> {
public MultiHashMap() { }
public MultiHashMap(int size) {
super(size);
}
public void addToList(K key, V value) {
ArrayList<V> list = get(key);
if (list == null) {
list = new ArrayList<>();
list.add(value);
put(key, list);
} else {
list.add(value);
}
}
@Override
public MultiHashMap<K, V> clone() {
MultiHashMap<K, V> map = new MultiHashMap<>(size());
for (Entry<K, ArrayList<V>> entry : entrySet()) {
map.put(entry.getKey(), new ArrayList<V>(entry.getValue()));
}
return map;
}
}
@@ -44,7 +44,8 @@ public class OnboardingPrefs<T extends Launcher> {
*/
@StringDef(value = {
HOME_BOUNCE_SEEN,
SHELF_BOUNCE_SEEN
SHELF_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {}
@@ -86,10 +86,6 @@ public class ArrowTipView extends AbstractFloatingView {
}
}
@Override
public void logActionCommand(int command) {
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ON_BOARD_POPUP) != 0;
@@ -121,9 +121,6 @@ public class FloatingSurfaceView extends AbstractFloatingView implements
launcher.getDragLayer().addView(view);
}
@Override
public void logActionCommand(int command) { }
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ICON_SURFACE) != 0;
@@ -37,20 +37,20 @@ import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
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;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import java.util.ArrayList;
import java.util.List;
/**
* A view representing a high confidence app search result that includes shortcuts
*/
public class HeroSearchResultView extends LinearLayout implements DragSource,
AllAppsSearchBarController.PayloadResultHandler<List<WorkspaceItemInfo>> {
AllAppsSearchBarController.PayloadResultHandler<List<ItemInfoWithIcon>> {
public static final int MAX_SHORTCUTS_COUNT = 2;
BubbleTextView mBubbleTextView;
View mIconView;
BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
@@ -99,15 +99,15 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
* Apply {@link ItemInfo} for appIcon and shortcut Icons
*/
@Override
public void applyAdapterInfo(AdapterItemWithPayload<List<WorkspaceItemInfo>> adapterItem) {
public void applyAdapterInfo(AdapterItemWithPayload<List<ItemInfoWithIcon>> adapterItem) {
mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
mIconView.setBackground(mBubbleTextView.getIcon());
mIconView.setTag(adapterItem.appInfo);
List<WorkspaceItemInfo> shorcutInfos = adapterItem.getPayload();
List<ItemInfoWithIcon> shorcutInfos = adapterItem.getPayload();
for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
if (i < shorcutInfos.size()) {
mDeepShortcutTextViews[i].applyFromWorkspaceItem(shorcutInfos.get(i));
mDeepShortcutTextViews[i].applyFromItemInfoWithIcon(shorcutInfos.get(i));
}
}
}
@@ -118,12 +118,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
mBubbleTextView.setIconVisible(true);
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> parents) {
}
private void setWillDrawIcon(boolean willDraw) {
mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
}
@@ -84,11 +84,6 @@ public class ListenerView extends AbstractFloatingView {
mIsOpen = false;
}
@Override
public void logActionCommand(int command) {
// Users do not interact with FloatingIconView, so there is nothing to log here.
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_LISTENER) != 0;
@@ -107,11 +107,6 @@ public class OptionsPopupView extends ArrowPopup
return true;
}
@Override
public void logActionCommand(int command) {
// TODO:
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_OPTIONS_POPUP) != 0;
@@ -32,6 +32,7 @@ import androidx.core.graphics.ColorUtils;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
import com.android.launcher3.util.Themes;
@@ -41,6 +42,7 @@ import com.android.launcher3.util.Themes;
*/
public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
private static final float SCRIM_ALPHA = .75f;
protected final T mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
protected final int mEndScrim;
@@ -59,7 +61,11 @@ public class ScrimView<T extends Launcher> extends View implements Insettable, O
super(context, attrs);
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255 * SCRIM_ALPHA));
}
mEndScrim = endScrim;
mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
mMaxScrimAlpha = 0.7f;
@@ -0,0 +1,102 @@
/*
* 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.views;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import java.util.ArrayList;
/**
* A row of tappable TextViews with a breadcrumb for settings search.
*/
public class SearchSettingsRowView extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mBreadcrumbsView;
private Intent mIntent;
public SearchSettingsRowView(@NonNull Context context) {
super(context);
}
public SearchSettingsRowView(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitleView = findViewById(R.id.title);
mDescriptionView = findViewById(R.id.description);
mBreadcrumbsView = findViewById(R.id.breadcrumbs);
setOnClickListener(this);
}
@Override
public void applyAdapterInfo(
AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
mIntent = bundle.getParcelable("intent");
showIfAvailable(mTitleView, bundle.getString("title"));
showIfAvailable(mDescriptionView, bundle.getString("description"));
ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
//TODO: implement RTL friendly breadcrumbs view
showIfAvailable(mBreadcrumbsView, breadcrumbs != null
? String.join(" > ", breadcrumbs) : null);
adapterItemWithPayload.setSelectionHandler(() -> onClick(this));
}
private void showIfAvailable(TextView view, @Nullable String string) {
if (TextUtils.isEmpty(string)) {
view.setVisibility(GONE);
} else {
view.setVisibility(VISIBLE);
view.setText(string);
}
}
@Override
public void onClick(View view) {
if (mIntent == null) return;
// TODO: create ItemInfo object and then use it to call startActivityForResult for proper
// WW logging
Launcher launcher = Launcher.getLauncher(view.getContext());
launcher.startActivityForResult(mIntent, 0);
}
}
@@ -166,11 +166,6 @@ public class Snackbar extends AbstractFloatingView {
}
}
@Override
public void logActionCommand(int command) {
// TODO
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_SNACKBAR) != 0;
@@ -38,7 +38,6 @@ import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
/**
* On boarding flow for users right after setting up work profile
@@ -88,16 +87,6 @@ public class WorkEduView extends AbstractSlideInView
mLauncher.getStateManager().removeStateListener(this);
}
@Override
public void logActionCommand(int command) {
// Since this is on-boarding popup, it is not a user controlled action.
}
@Override
public int getLogContainerType() {
return LauncherLogProto.ContainerType.TIP;
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ON_BOARD_POPUP) != 0;
@@ -16,7 +16,6 @@
package com.android.launcher3.widget;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import android.content.Context;
import android.graphics.Point;
@@ -31,20 +30,15 @@ import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.AbstractSlideInView;
import java.util.ArrayList;
/**
* Base class for various widgets popup
*/
@@ -149,28 +143,6 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, Target child,
ArrayList<Target> parents) {
Target target = newContainerTarget(ContainerType.WIDGETS);
target.cardinality = getElementsRowCount();
parents.add(target);
}
@Override
public final void logActionCommand(int command) {
Target target = newContainerTarget(getLogContainerType());
target.cardinality = getElementsRowCount();
mLauncher.getUserEventDispatcher().logActionCommand(command, target);
}
@Override
public int getLogContainerType() {
return ContainerType.WIDGETS;
}
protected abstract int getElementsRowCount();
protected SystemUiController getSystemUiController() {
return mLauncher.getSystemUiController();
}
@@ -179,11 +179,6 @@ public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
setPadding(leftInset, getPaddingTop(), rightInset, bottomInset);
}
@Override
protected int getElementsRowCount() {
return 1;
}
@Override
protected Pair<View, String> getAccessibilityTarget() {
return Pair.create(findViewById(R.id.title), getContext().getString(
@@ -242,11 +242,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
return launcher.findViewById(R.id.widgets_list_view);
}
@Override
protected int getElementsRowCount() {
return mAdapter.getItemCount();
}
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
@@ -23,9 +23,12 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import com.android.launcher3.icons.IconCache;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.LabelComparator;
@@ -34,9 +37,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
/**
* List view adapter for the widget tray.
*
@@ -128,7 +128,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
int childCount = row.getChildCount();
if (expectedChildCount > childCount) {
for (int i = childCount ; i < expectedChildCount; i++) {
for (int i = childCount; i < expectedChildCount; i++) {
if ((i & 1) == 1) {
// Add a divider for odd index
mLayoutInflater.inflate(R.layout.widget_list_divider, row);
@@ -144,24 +144,24 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
}
}
} else if (expectedChildCount < childCount) {
for (int i = expectedChildCount ; i < childCount; i++) {
for (int i = expectedChildCount; i < childCount; i++) {
row.getChildAt(i).setVisibility(View.GONE);
}
}
// Bind the views in the application info section.
holder.title.applyFromPackageItemInfo(entry.pkgItem);
holder.title.applyFromItemInfoWithIcon(entry.pkgItem);
// Bind the view in the widget horizontal tray region.
for (int i=0; i < infoList.size(); i++) {
WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
for (int i = 0; i < infoList.size(); i++) {
WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
widget.ensurePreview();
widget.setVisibility(View.VISIBLE);
if (i > 0) {
row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
}
}
}
@@ -185,7 +185,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
@Override
public void onViewRecycled(WidgetsRowViewHolder holder) {
int total = holder.cellContainer.getChildCount();
for (int i = 0; i < total; i+=2) {
for (int i = 0; i < total; i += 2) {
WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
widget.clear();
}
@@ -1,34 +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.systemui.plugins;
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
* Implement this plugin interface to access user event log on the device for prototype purpose.
* NOTE: plugin is for internal prototype only and is not visible in production environment.
*/
@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION)
public interface UserEventPlugin extends Plugin {
String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG";
int VERSION = 1;
/**
* Callback to be triggered whenever an user event occurs.
*/
void onUserEvent(Object event);
}
@@ -26,7 +26,6 @@ import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.widget.WidgetItemComparator;
@@ -36,11 +35,12 @@ import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
@@ -57,9 +57,7 @@ public class WidgetsModel {
private static final boolean DEBUG = false;
/* Map of widgets and shortcuts that are tracked per package. */
private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList = new MultiHashMap<>();
private AppFilter mAppFilter;
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
/**
* Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
@@ -74,8 +72,9 @@ public class WidgetsModel {
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
WidgetItemComparator widgetComparator = new WidgetItemComparator();
for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
WidgetListRowEntry row = new WidgetListRowEntry(
entry.getKey(), new ArrayList<>(entry.getValue()));
row.titleSectionName = (row.pkgItem.title == null) ? "" :
indexer.computeSectionName(row.pkgItem.title);
Collections.sort(row.widgets, widgetComparator);
@@ -146,77 +145,42 @@ public class WidgetsModel {
if (packageUser == null) {
mWidgetsList.clear();
} else {
// Only clear the widgets for the given package/user.
PackageItemInfo packageItem = null;
for (PackageItemInfo item : mWidgetsList.keySet()) {
if (item.packageName.equals(packageUser.mPackageName)) {
packageItem = item;
break;
}
}
PackageItemInfo packageItem = mWidgetsList.keySet()
.stream()
.filter(item -> item.packageName.equals(packageUser.mPackageName))
.findFirst()
.orElse(null);
if (packageItem != null) {
// We want to preserve the user that was on the packageItem previously,
// so add it to tmpPackageItemInfos here to avoid creating a new entry.
tmpPackageItemInfos.put(packageItem.packageName, packageItem);
Iterator<WidgetItem> widgetItemIterator = mWidgetsList.get(packageItem).iterator();
while (widgetItemIterator.hasNext()) {
WidgetItem nextWidget = widgetItemIterator.next();
if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName)
&& nextWidget.user.equals(packageUser.mUser)) {
widgetItemIterator.remove();
}
}
// Add the widgets for other users in the rawList as it only contains widgets for
// packageUser
List<WidgetItem> otherUserItems = mWidgetsList.remove(packageItem);
otherUserItems.removeIf(w -> w.user.equals(packageUser.mUser));
rawWidgetsShortcuts.addAll(otherUserItems);
}
}
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
UserHandle myUser = Process.myUserHandle();
// add and update.
for (WidgetItem item : rawWidgetsShortcuts) {
if (item.widgetInfo != null) {
if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
// Widget is hidden from picker
continue;
}
// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
if (minSpanX > idp.numColumns || minSpanY > idp.numRows) {
if (DEBUG) {
Log.d(TAG, String.format(
"Widget %s : (%d X %d) can't fit on this device",
item.componentName, minSpanX, minSpanY));
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
.collect(Collectors.groupingBy(item -> {
String packageName = item.componentName.getPackageName();
PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
if (pInfo == null) {
pInfo = new PackageItemInfo(packageName);
pInfo.user = item.user;
tmpPackageItemInfos.put(packageName, pInfo);
} else if (!myUser.equals(pInfo.user)) {
// Keep updating the user, until we get the primary user.
pInfo.user = item.user;
}
continue;
}
}
if (mAppFilter == null) {
mAppFilter = AppFilter.newInstance(app.getContext());
}
if (!mAppFilter.shouldShowApp(item.componentName)) {
if (DEBUG) {
Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
item.componentName));
}
continue;
}
String packageName = item.componentName.getPackageName();
PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
if (pInfo == null) {
pInfo = new PackageItemInfo(packageName);
pInfo.user = item.user;
tmpPackageItemInfos.put(packageName, pInfo);
} else if (!myUser.equals(pInfo.user)) {
// Keep updating the user, until we get the primary user.
pInfo.user = item.user;
}
mWidgetsList.addToList(pInfo, item);
}
return pInfo;
})));
// Update each package entry
IconCache iconCache = app.getIconCache();
@@ -227,9 +191,9 @@ public class WidgetsModel {
public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
LauncherAppState app) {
for (Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
if (packageNames.contains(entry.getKey().packageName)) {
ArrayList<WidgetItem> items = entry.getValue();
List<WidgetItem> items = entry.getValue();
int count = items.size();
for (int i = 0; i < count; i++) {
WidgetItem item = items.get(i);
@@ -249,7 +213,7 @@ public class WidgetsModel {
public WidgetItem getWidgetProviderInfoByProviderName(
ComponentName providerName) {
ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
List<WidgetItem> widgetsList = mWidgetsList.get(
new PackageItemInfo(providerName.getPackageName()));
if (widgetsList == null) {
return null;
@@ -262,4 +226,46 @@ public class WidgetsModel {
}
return null;
}
private static class WidgetValidityCheck implements Predicate<WidgetItem> {
private final InvariantDeviceProfile mIdp;
private final AppFilter mAppFilter;
WidgetValidityCheck(LauncherAppState app) {
mIdp = app.getInvariantDeviceProfile();
mAppFilter = AppFilter.newInstance(app.getContext());
}
@Override
public boolean test(WidgetItem item) {
if (item.widgetInfo != null) {
if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
// Widget is hidden from picker
return false;
}
// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
if (minSpanX > mIdp.numColumns || minSpanY > mIdp.numRows) {
if (DEBUG) {
Log.d(TAG, String.format(
"Widget %s : (%d X %d) can't fit on this device",
item.componentName, minSpanX, minSpanY));
}
return false;
}
}
if (!mAppFilter.shouldShowApp(item.componentName)) {
if (DEBUG) {
Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
item.componentName));
}
return false;
}
return true;
}
}
}