Merging from ub-launcher3-rvc-dev @ build 6595814

Bug:150504032
Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-rvc-dev_rvc-dev_6595814.html

Change-Id: I4be85d0e52cc3cb8801012a7313a269851680c8b
This commit is contained in:
Miranda Kephart
2020-06-16 20:52:28 +00:00
68 changed files with 1113 additions and 497 deletions
+4
View File
@@ -54,6 +54,7 @@ message ContainerInfo {
ShortcutsContainer shortcuts_container = 8; ShortcutsContainer shortcuts_container = 8;
SettingsContainer settings_container = 9; SettingsContainer settings_container = 9;
PredictedHotseatContainer predicted_hotseat_container = 10; PredictedHotseatContainer predicted_hotseat_container = 10;
TaskSwitcherContainer task_switcher_container = 11;
} }
} }
@@ -82,6 +83,9 @@ message ShortcutsContainer {
message SettingsContainer { message SettingsContainer {
} }
message TaskSwitcherContainer {
}
enum Attribute { enum Attribute {
UNKNOWN = 0; UNKNOWN = 0;
DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
@@ -18,6 +18,7 @@ package com.android.launcher3.appprediction;
import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_RANKED;
import android.app.prediction.AppPredictor; import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget; import android.app.prediction.AppTarget;
@@ -37,6 +38,8 @@ import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.shortcuts.ShortcutKey;
@@ -48,6 +51,7 @@ import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream; import java.util.stream.IntStream;
/** /**
@@ -301,6 +305,41 @@ public class PredictionUiStateManager implements StateListener<LauncherState>,
return mCurrentState; return mCurrentState;
} }
/**
* Logs ranking info for launched app within all apps prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
* {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
* {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
* {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
*/
public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
if (itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = getCurrentState().apps;
OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst();
if (!rank.isPresent()) {
return;
}
LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder();
atomBuilder.setRank(rank.getAsInt());
atomBuilder.setContainerInfo(
LauncherAtom.ContainerInfo.newBuilder().setPredictionContainer(
LauncherAtom.PredictionContainer.newBuilder().build()).build());
launcher.getStatsLogManager().log(LAUNCHER_ALL_APPS_RANKED, instanceId,
atomBuilder.build());
}
/** /**
* Fill in predicted_rank field based on app prediction. * Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings: * Only applicable when {@link ItemInfo#itemType} is one of the followings:
@@ -310,6 +349,7 @@ public class PredictionUiStateManager implements StateListener<LauncherState>,
*/ */
public static void fillInPredictedRank( public static void fillInPredictedRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
@@ -15,6 +15,9 @@
*/ */
package com.android.launcher3.hybridhotseat; package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
.LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
import android.content.Intent; import android.content.Intent;
import android.view.View; import android.view.View;
@@ -47,12 +50,12 @@ public class HotseatEduController {
public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen"; public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
public static final String HOTSEAT_EDU_ACTION = public static final String HOTSEAT_EDU_ACTION =
"com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"; "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
private static final String SETTINGS_ACTION = public static final String SETTINGS_ACTION =
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
private final Launcher mLauncher; private final Launcher mLauncher;
private final Hotseat mHotseat; private final Hotseat mHotseat;
private final HotseatRestoreHelper mRestoreHelper; private HotseatRestoreHelper mRestoreHelper;
private List<WorkspaceItemInfo> mPredictedApps; private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog; private HotseatEduDialog mActiveDialog;
@@ -71,14 +74,17 @@ public class HotseatEduController {
* Checks what type of migration should be used and migrates hotseat * Checks what type of migration should be used and migrates hotseat
*/ */
void migrate() { void migrate() {
mRestoreHelper.createBackup(); if (mRestoreHelper != null) {
mRestoreHelper.createBackup();
}
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
migrateToFolder(); migrateToFolder();
} else { } else {
migrateHotseatWhole(); migrateHotseatWhole();
} }
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled, R.string.hotseat_turn_off, Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION))); R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
} }
/** /**
@@ -223,15 +229,15 @@ public class HotseatEduController {
void finishOnboarding() { void finishOnboarding() {
mOnOnboardingComplete.run(); mOnOnboardingComplete.run();
destroy();
mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply(); mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
} }
void showDimissTip() { void showDimissTip() {
if (mHotseat.getShortcutsAndWidgets().getChildCount() if (mHotseat.getShortcutsAndWidgets().getChildCount()
< mLauncher.getDeviceProfile().inv.numHotseatIcons) { < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off, Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION))); R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
} else { } else {
new ArrowTipView(mLauncher).show( new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop()); mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
@@ -242,12 +248,6 @@ public class HotseatEduController {
mPredictedApps = predictedApps; mPredictedApps = predictedApps;
} }
void destroy() {
if (mActiveDialog != null) {
mActiveDialog.setHotseatEduController(null);
}
}
void showEdu() { void showEdu() {
int childCount = mHotseat.getShortcutsAndWidgets().getChildCount(); int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID); CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
@@ -265,6 +265,7 @@ public class HotseatEduController {
requiresMigration ? R.string.hotseat_tip_no_empty_slots requiresMigration ? R.string.hotseat_tip_no_empty_slots
: R.string.hotseat_auto_enrolled), : R.string.hotseat_auto_enrolled),
mHotseat.getTop()); mHotseat.getTop());
mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
finishOnboarding(); finishOnboarding();
} }
} }
@@ -15,9 +15,10 @@
*/ */
package com.android.launcher3.hybridhotseat; package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType .LAUNCHER_HOTSEAT_EDU_ACCEPT;
.HYBRID_HOTSEAT_CANCELED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
import android.animation.PropertyValuesHolder; import android.animation.PropertyValuesHolder;
import android.content.Context; import android.content.Context;
@@ -29,15 +30,14 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout; import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable; import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -111,15 +111,13 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
mHotseatEduController.moveHotseatItems(); mHotseatEduController.moveHotseatItems();
mHotseatEduController.finishOnboarding(); mHotseatEduController.finishOnboarding();
//TODO: pass actual page index here. mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_ACCEPT);
// Temporarily we're passing 1 for folder migration and 2 for page migration
logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
} }
private void onDismiss(View v) { private void onDismiss(View v) {
mHotseatEduController.showDimissTip(); mHotseatEduController.showDimissTip();
mHotseatEduController.finishOnboarding(); mHotseatEduController.finishOnboarding();
logUserAction(false, -1); mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_DENY);
handleClose(true); handleClose(true);
} }
@@ -163,39 +161,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
} }
} }
private void logUserAction(boolean migrated, int pageIndex) {
LauncherLogProto.Action action = new LauncherLogProto.Action();
LauncherLogProto.Target target = new LauncherLogProto.Target();
int hotseatItemsCount = mLauncher.getHotseat().getShortcutsAndWidgets().getChildCount();
// -1 to exclude smart space
int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
action.type = LauncherLogProto.Action.Type.TOUCH;
action.touch = LauncherLogProto.Action.Touch.TAP;
target.containerType = LauncherLogProto.ContainerType.TIP;
target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
: HYBRID_HOTSEAT_CANCELED;
target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
// encoding migration type on pageIndex
target.pageIndex = pageIndex;
target.cardinality = (workspaceItemCount * 1000) + hotseatItemsCount;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
}
private void logOnBoardingSeen() {
LauncherLogProto.Action action = new LauncherLogProto.Action();
LauncherLogProto.Target target = new LauncherLogProto.Target();
action.type = LauncherLogProto.Action.Type.TIP;
target.containerType = LauncherLogProto.ContainerType.TIP;
target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
}
private void animateOpen() { private void animateOpen() {
if (mIsOpen || mOpenCloseAnimator.isRunning()) { if (mIsOpen || mOpenCloseAnimator.isRunning()) {
return; return;
@@ -244,8 +209,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
|| mHotseatEduController == null) { || mHotseatEduController == null) {
return; return;
} }
AbstractFloatingView.closeAllOpenViews(mLauncher);
attachToContainer(); attachToContainer();
logOnBoardingSeen(); mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_SEEN);
animateOpen(); animateOpen();
populatePreview(predictions); populatePreview(predictions);
} }
@@ -0,0 +1,129 @@
/*
* 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.hybridhotseat;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MainThreadInitializedObject;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* Helper class to allow hot seat file logging
*/
public class HotseatFileLog {
public static final int LOG_DAYS = 10;
private static final String FILE_NAME_PREFIX = "hotseat-log-";
private static final DateFormat DATE_FORMAT =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
public static final MainThreadInitializedObject<HotseatFileLog> INSTANCE =
new MainThreadInitializedObject<>(HotseatFileLog::new);
private final Handler mHandler = new Handler(
Executors.createAndStartNewLooper("hotseat-logger"));
private final File mLogsDir;
private PrintWriter mCurrentWriter;
private String mFileName;
private HotseatFileLog(Context context) {
mLogsDir = context.getFilesDir();
}
/**
* Prints log values to disk
*/
public void log(String tag, String msg) {
String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
mHandler.post(() -> {
synchronized (this) {
PrintWriter writer = getWriter();
if (writer != null) {
writer.println(out);
}
}
});
}
private PrintWriter getWriter() {
String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
if (fName.equals(mFileName)) return mCurrentWriter;
Calendar cal = Calendar.getInstance();
boolean append = false;
File logFile = new File(mLogsDir, fName);
if (logFile.exists()) {
Calendar modifiedTime = Calendar.getInstance();
modifiedTime.setTimeInMillis(logFile.lastModified());
// If the file was modified more that 36 hours ago, purge the file.
// We use instead of 24 to account for day-365 followed by day-1
modifiedTime.add(Calendar.HOUR, 36);
append = cal.before(modifiedTime);
}
if (mCurrentWriter != null) {
mCurrentWriter.close();
}
try {
mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
mFileName = fName;
} catch (Exception ex) {
Log.e("HotseatLogs", "Error writing logs to file", ex);
closeWriter();
}
return mCurrentWriter;
}
private synchronized void closeWriter() {
mFileName = null;
if (mCurrentWriter != null) {
mCurrentWriter.close();
}
mCurrentWriter = null;
}
/**
* Returns a list of all log files
*/
public synchronized File[] getLogFiles() {
File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS];
//include file log files here
System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS);
closeWriter();
for (int i = 0; i < LOG_DAYS; i++) {
files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i);
}
return files;
}
}
@@ -17,6 +17,8 @@ package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.hybridhotseat.HotseatEduController.SETTINGS_ACTION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
@@ -27,6 +29,8 @@ import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget; import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetEvent;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent;
import android.os.Process;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -50,7 +54,8 @@ import com.android.launcher3.appprediction.DynamicItemCache;
import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog; import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
@@ -64,6 +69,8 @@ import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntArray;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.Snackbar;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@@ -107,8 +114,6 @@ public class HotseatPredictionController implements DragController.DragListener,
private boolean mIsCacheEmpty; private boolean mIsCacheEmpty;
private boolean mIsDestroyed = false; private boolean mIsDestroyed = false;
private HotseatEduController mHotseatEduController;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>(); private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
@@ -146,11 +151,47 @@ public class HotseatPredictionController implements DragController.DragListener,
} }
/** /**
* Transitions to NORMAL workspace mode and shows edu * Shows appropriate hotseat education based on prediction enabled and migration states.
*/ */
public void showEdu() { public void showEdu() {
if (mHotseatEduController == null) return; if (mComponentKeyMappers.isEmpty()) {
mHotseatEduController.showEdu(); // launcher has empty predictions set
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(
new Intent(SETTINGS_ACTION)));
} else if (isEduSeen()) {
// user has already went through education
new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotsaet_tip_prediction_enabled),
mHotseat.getTop());
} else {
HotseatEduController eduController = new HotseatEduController(mLauncher, mRestoreHelper,
this::createPredictor);
eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
eduController.showEdu();
}
}
/**
* Shows educational tip for hotseat if user does not go through Tips app.
*/
public void showDiscoveryTip() {
if (getPredictedIcons().size() == mHotSeatItemsCount) {
new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
} else {
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
}
}
/**
* Returns if hotseat client has predictions
*/
public boolean hasPredictions() {
return !mComponentKeyMappers.isEmpty();
} }
@Override @Override
@@ -250,10 +291,6 @@ public class HotseatPredictionController implements DragController.DragListener,
if (mAppPredictor != null) { if (mAppPredictor != null) {
mAppPredictor.destroy(); mAppPredictor.destroy();
} }
if (mHotseatEduController != null) {
mHotseatEduController.destroy();
mHotseatEduController = null;
}
} }
/** /**
@@ -299,20 +336,20 @@ public class HotseatPredictionController implements DragController.DragListener,
mAppPredictor.requestPredictionUpdate(); mAppPredictor.requestPredictionUpdate();
}); });
setPauseUIUpdate(false); setPauseUIUpdate(false);
if (!isEduSeen()) {
mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper,
this::createPredictor);
}
} }
/** /**
* Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items. * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
*/ */
public void showCachedItems(List<AppInfo> apps, IntArray ranks) { public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
if (hasPredictions() && mAppPredictor != null) {
mAppPredictor.requestPredictionUpdate();
fillGapsWithPrediction();
return;
}
mIsCacheEmpty = apps.isEmpty(); mIsCacheEmpty = apps.isEmpty();
int count = Math.min(ranks.size(), apps.size()); int count = Math.min(ranks.size(), apps.size());
List<WorkspaceItemInfo> items = new ArrayList<>(count); List<WorkspaceItemInfo> items = new ArrayList<>(count);
mComponentKeyMappers.clear();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i)); WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user); ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
@@ -324,6 +361,7 @@ public class HotseatPredictionController implements DragController.DragListener,
updateDependencies(); updateDependencies();
bindItems(items, false, null); bindItems(items, false, null);
} }
private void setPredictedApps(List<AppTarget> appTargets) { private void setPredictedApps(List<AppTarget> appTargets) {
mComponentKeyMappers.clear(); mComponentKeyMappers.clear();
if (appTargets.isEmpty()) { if (appTargets.isEmpty()) {
@@ -347,12 +385,11 @@ public class HotseatPredictionController implements DragController.DragListener,
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
} }
predictionLog.append("]"); predictionLog.append("]");
if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString()); if (Utilities.IS_DEBUG_DEVICE) {
HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
}
updateDependencies(); updateDependencies();
fillGapsWithPrediction(); fillGapsWithPrediction();
if (!isEduSeen() && mHotseatEduController != null) {
mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
}
cachePredictionComponentKeysIfNecessary(componentKeys); cachePredictionComponentKeysIfNecessary(componentKeys);
} }
@@ -602,6 +639,48 @@ public class HotseatPredictionController implements DragController.DragListener,
mHotseat.fillInLogContainerData(childInfo, child, parents); mHotseat.fillInLogContainerData(childInfo, child, parents);
} }
/**
* Logs rank info based on current list of predicted items
*/
public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
if (Utilities.IS_DEBUG_DEVICE) {
final String pkg = itemInfo.getTargetComponent() != null
? itemInfo.getTargetComponent().getPackageName() : "unknown";
HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
"appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
&& !Process.myUserHandle().equals(itemInfo.user))
+ ",launchLocation:" + itemInfo.container);
}
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst();
if (!rank.isPresent()) {
return;
}
LauncherAtom.PredictedHotseatContainer.Builder containerBuilder =
LauncherAtom.PredictedHotseatContainer.newBuilder();
LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder();
int cardinality = 0;
for (PredictedAppIcon icon : getPredictedIcons()) {
ItemInfo info = (ItemInfo) icon.getTag();
cardinality |= 1 << info.screenId;
}
containerBuilder.setCardinality(cardinality);
atomBuilder.setRank(rank.getAsInt());
if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
containerBuilder.setIndex(rank.getAsInt());
}
atomBuilder.setContainerInfo(
LauncherAtom.ContainerInfo.newBuilder().setPredictedHotseatContainer(
containerBuilder).build());
mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_RANKED, instanceId,
atomBuilder.build());
}
private class PinPrediction extends SystemShortcut<QuickstepLauncher> { private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
@@ -29,7 +29,6 @@ import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@@ -42,10 +41,12 @@ import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace; import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.Folder;
import com.android.launcher3.hybridhotseat.HotseatEduController; import com.android.launcher3.hybridhotseat.HotseatEduController;
import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -88,11 +89,10 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
*/ */
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
private HotseatPredictionController mHotseatPredictionController;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void setupViews() {
super.onCreate(savedInstanceState); super.setupViews();
if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) { if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
mHotseatPredictionController = new HotseatPredictionController(this); mHotseatPredictionController = new HotseatPredictionController(this);
mHotseatPredictionController.createPredictor(); mHotseatPredictionController.createPredictor();
@@ -113,6 +113,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
} }
} }
@Override
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
super.logAppLaunch(info, instanceId);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
PredictionUiStateManager.INSTANCE.get(this).logLaunchedAppRankingInfo(info, instanceId);
}
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
@@ -168,13 +177,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
} }
} }
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
/** /**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes. * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
*/ */
@@ -20,6 +20,7 @@ import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsView;
@@ -56,6 +57,17 @@ public class OverviewModalTaskState extends OverviewState {
return 1.0f; return 1.0f;
} }
@Override
public void onBackPressed(Launcher launcher) {
launcher.getStateManager().goToState(LauncherState.OVERVIEW);
RecentsView recentsView = launcher.<RecentsView>getOverviewPanel();
if (recentsView != null) {
recentsView.resetModalVisuals();
} else {
super.onBackPressed(launcher);
}
}
public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) { public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
Rect out = new Rect(); Rect out = new Rect();
activity.<RecentsView>getOverviewPanel().getTaskSize(out); activity.<RecentsView>getOverviewPanel().getTaskSize(out);
@@ -156,6 +156,7 @@ public class QuickstepAtomicAnimationFactory extends
if (toState == NORMAL && fromState == OVERVIEW) { if (toState == NORMAL && fromState == OVERVIEW) {
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
@@ -210,6 +211,7 @@ public class QuickstepAtomicAnimationFactory extends
} }
} }
config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2); config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get() Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
@@ -32,7 +32,6 @@ import androidx.annotation.CallSuper;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.VibratorWrapper;
@@ -96,8 +95,8 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
* depend on proper class initialization. * depend on proper class initialization.
*/ */
protected void initAfterSubclassConstructor() { protected void initAfterSubclassConstructor() {
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) initTransitionEndpoints(
.getDeviceProfile(mContext)); mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
} }
protected void performHapticFeedback() { protected void performHapticFeedback() {
@@ -144,13 +143,14 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
TaskView nextTask = mRecentsView.getTaskView(taskId); TaskView nextTask = mRecentsView.getTaskView(taskId);
if (nextTask != null) { if (nextTask != null) {
mGestureState.updateLastStartedTaskId(taskId); mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
nextTask.launchTask(false /* animate */, true /* freezeTaskList */, nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> { success -> {
resultCallback.accept(success); resultCallback.accept(success);
if (success) { if (success) {
if (mRecentsView.indexOfChild(nextTask) if (hasTaskPreviouslyAppeared) {
== getLastAppearedTaskIndex()) { onRestartPreviouslyAppearedTask();
onRestartLastAppearedTask();
} }
} else { } else {
mActivityInterface.onLaunchTaskFailed(); mActivityInterface.onLaunchTaskFailed();
@@ -171,7 +171,7 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
* start A again to ensure it stays on top. * start A again to ensure it stays on top.
*/ */
@CallSuper @CallSuper
protected void onRestartLastAppearedTask() { protected void onRestartPreviouslyAppearedTask() {
// Finish the controller here, since we won't get onTaskAppeared() for a task that already // Finish the controller here, since we won't get onTaskAppeared() for a task that already
// appeared. // appeared.
if (mRecentsAnimationController != null) { if (mRecentsAnimationController != null) {
@@ -205,7 +205,7 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
mRecentsAnimationController = recentsAnimationController; mRecentsAnimationController = recentsAnimationController;
mRecentsAnimationTargets = targets; mRecentsAnimationTargets = targets;
mTransformParams.setTargetSet(mRecentsAnimationTargets); mTransformParams.setTargetSet(mRecentsAnimationTargets);
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask( RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
mGestureState.getRunningTaskId()); mGestureState.getRunningTaskId());
@@ -300,8 +300,7 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
if (TestProtocol.sDebugTracing) { if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2"); Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
} }
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) initTransitionEndpoints(createdActivity.getDeviceProfile());
.getDeviceProfile(mContext));
} }
return true; return true;
} }
@@ -1122,8 +1122,8 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
} }
@Override @Override
protected void onRestartLastAppearedTask() { protected void onRestartPreviouslyAppearedTask() {
super.onRestartLastAppearedTask(); super.onRestartPreviouslyAppearedTask();
reset(); reset();
} }
@@ -37,7 +37,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: { case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight = final float swipeHeight =
LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile, LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
PagedOrientationHandler.HOME_ROTATED); PagedOrientationHandler.PORTRAIT);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
return response; return response;
} }
@@ -139,13 +139,12 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
*/ */
protected DeviceProfile createDeviceProfile() { protected DeviceProfile createDeviceProfile() {
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this); DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
DeviceProfile dp1 = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
// In case we are reusing IDP, create a copy so that we don't conflict with Launcher // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
// activity. // activity.
return (mRecentsRootView != null) && isInMultiWindowMode() return (mRecentsRootView != null) && isInMultiWindowMode()
? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize()) ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
: dp1.copy(this); : dp.copy(this);
} }
@Override @Override
@@ -83,15 +83,15 @@ public abstract class SwipeUpAnimationLogic {
mGestureState = gestureState; mGestureState = gestureState;
mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
mTransformParams = transformParams; mTransformParams = transformParams;
mTaskViewSimulator.setLayoutRotation(
mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
} }
protected void initTransitionEndpoints(DeviceProfile dp) { protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp; mDp = dp;
mTaskViewSimulator.setDp(dp); mTaskViewSimulator.setDp(dp);
mTaskViewSimulator.setLayoutRotation(
mDeviceState.getCurrentActiveRotation(),
mDeviceState.getDisplayRotation());
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
dp, mContext, TEMP_RECT, dp, mContext, TEMP_RECT,
mTaskViewSimulator.getOrientationState().getOrientationHandler()); mTaskViewSimulator.getOrientationState().getOrientationHandler());
@@ -16,6 +16,8 @@
package com.android.quickstep; package com.android.quickstep;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -24,6 +26,7 @@ import android.graphics.Insets;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
@@ -31,9 +34,12 @@ import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.util.ResourceBasedOverride;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView;
@@ -58,6 +64,19 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
shortcuts.add(shortcut); shortcuts.add(shortcut);
} }
} }
RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
boolean canLauncherRotate = orientedState.canLauncherRotate();
boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
// Add overview actions to the menu when in in-place rotate landscape mode.
if (!canLauncherRotate && isInLandscape) {
for (TaskShortcutFactory actionMenuOption : ACTION_MENU_OPTIONS) {
SystemShortcut shortcut = actionMenuOption.getShortcut(activity, taskView);
if (shortcut != null) {
shortcuts.add(shortcut);
}
}
}
return shortcuts; return shortcuts;
} }
@@ -85,6 +104,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
TaskShortcutFactory.WELLBEING TaskShortcutFactory.WELLBEING
}; };
private static final TaskShortcutFactory[] ACTION_MENU_OPTIONS = new TaskShortcutFactory[]{
TaskShortcutFactory.SCREENSHOT,
TaskShortcutFactory.MODAL
};
/** /**
* Overlay on each task handling Overview Action Buttons. * Overlay on each task handling Overview Action Buttons.
*/ */
@@ -94,10 +118,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
protected final TaskThumbnailView mThumbnailView; protected final TaskThumbnailView mThumbnailView;
private T mActionsView; private T mActionsView;
private ImageActionsApi mImageApi;
private boolean mIsAllowedByPolicy;
protected TaskOverlay(TaskThumbnailView taskThumbnailView) { protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
mThumbnailView = taskThumbnailView; mThumbnailView = taskThumbnailView;
mImageApi = new ImageActionsApi(
mApplicationContext, mThumbnailView::getThumbnail);
} }
protected T getActionsView() { protected T getActionsView() {
@@ -112,15 +140,12 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
* Called when the current task is interactive for the user * Called when the current task is interactive for the user
*/ */
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
ImageActionsApi imageApi = new ImageActionsApi(
mApplicationContext, mThumbnailView::getThumbnail);
final boolean isAllowedByPolicy = thumbnail.isRealSnapshot; final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
getActionsView().setCallbacks(new OverlayUICallbacks() { getActionsView().setCallbacks(new OverlayUICallbacks() {
@Override @Override
public void onShare() { public void onShare() {
if (isAllowedByPolicy) { if (isAllowedByPolicy) {
imageApi.startShareActivity(); mImageApi.startShareActivity();
} else { } else {
showBlockedByPolicyMessage(); showBlockedByPolicyMessage();
} }
@@ -129,16 +154,23 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Override @Override
public void onScreenshot() { public void onScreenshot() {
if (isAllowedByPolicy) { saveScreenshot(task);
imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
} else {
showBlockedByPolicyMessage();
}
} }
}); });
} }
/**
* Called to save screenshot of the task thumbnail.
*/
@SuppressLint("NewApi")
private void saveScreenshot(Task task) {
if (mThumbnailView.isRealSnapshot()) {
mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
} else {
showBlockedByPolicyMessage();
}
}
/** /**
* Called when the overlay is no longer used. * Called when the overlay is no longer used.
@@ -146,6 +178,26 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
public void reset() { public void reset() {
} }
/**
* Called when the system wants to reset the modal visuals.
*/
public void resetModalVisuals() {
}
/**
* Gets the modal state system shortcut.
*/
public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) {
return null;
}
/**
* Gets the system shortcut for the screenshot that will be added to the task menu.
*/
public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
ItemInfo iteminfo) {
return new ScreenshotSystemShortcut(activity, iteminfo);
}
/** /**
* Gets the task snapshot as it is displayed on the screen. * Gets the task snapshot as it is displayed on the screen.
* *
@@ -175,6 +227,22 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
R.string.blocked_by_policy, R.string.blocked_by_policy,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
} }
private class ScreenshotSystemShortcut extends SystemShortcut {
private final BaseDraggingActivity mActivity;
ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) {
super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo);
mActivity = activity;
}
@Override
public void onClick(View view) {
saveScreenshot(mThumbnailView.getTaskView().getTask());
dismissTaskMenuView(mActivity);
}
}
} }
/** /**
@@ -18,29 +18,26 @@ package com.android.quickstep;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityOptions; import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.UserHandle;
import android.view.View; import android.view.View;
import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo; import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -69,28 +66,7 @@ public interface TaskShortcutFactory {
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view); SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
static WorkspaceItemInfo dummyInfo(TaskView view) { TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
Task task = view.getTask();
WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(){
/**
* Helps to log events as {@link LauncherAtom.Task}
* instead of {@link LauncherAtom.ItemInfo}.
*/
@Override
public LauncherAtom.ItemInfo buildProto() {
return view.buildProto();
}
};
dummyInfo.intent = new Intent();
ComponentName component = task.getTopComponent();
dummyInfo.getIntent().setComponent(component);
dummyInfo.user = UserHandle.of(task.key.userId);
dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
return dummyInfo;
}
TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
abstract class MultiWindowFactory implements TaskShortcutFactory { abstract class MultiWindowFactory implements TaskShortcutFactory {
@@ -134,7 +110,7 @@ public interface TaskShortcutFactory {
public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) { TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) {
super(iconRes, textRes, activity, dummyInfo(taskView)); super(iconRes, textRes, activity, taskView.getItemInfo());
mLauncherEvent = launcherEvent; mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper()); mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskView; mTaskView = taskView;
@@ -220,7 +196,7 @@ public interface TaskShortcutFactory {
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */, future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId); taskKey.displayId);
mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.buildProto()); mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.getItemInfo());
} }
} }
} }
@@ -304,7 +280,7 @@ public interface TaskShortcutFactory {
private final TaskView mTaskView; private final TaskView mTaskView;
public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) { public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv)); super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo());
mTaskView = tv; mTaskView = tv;
} }
@@ -321,15 +297,30 @@ public interface TaskShortcutFactory {
mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler()); mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
dismissTaskMenuView(mTarget); dismissTaskMenuView(mTarget);
mTarget.getStatsLogManager().log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP, mTarget.getStatsLogManager().log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP,
mTaskView.buildProto()); mTaskView.getItemInfo());
} }
} }
TaskShortcutFactory INSTALL = (activity, view) -> TaskShortcutFactory INSTALL = (activity, view) ->
InstantAppResolver.newInstance(activity).isInstantApp(activity, InstantAppResolver.newInstance(activity).isInstantApp(activity,
view.getTask().getTopComponent().getPackageName()) view.getTask().getTopComponent().getPackageName())
? new SystemShortcut.Install(activity, dummyInfo(view)) : null; ? new SystemShortcut.Install(activity, view.getItemInfo()) : null;
TaskShortcutFactory WELLBEING = (activity, view) -> TaskShortcutFactory WELLBEING = (activity, view) ->
WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view)); WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
if (ENABLE_OVERVIEW_ACTIONS.get()) {
return tv.getThumbnail().getTaskOverlay()
.getScreenshotShortcut(activity, tv.getItemInfo());
}
return null;
};
TaskShortcutFactory MODAL = (activity, tv) -> {
if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
}
return null;
};
} }
@@ -539,6 +539,8 @@ public class TouchInteractionService extends Service implements PluginListener<O
if (mTaskAnimationManager.isRecentsAnimationRunning()) { if (mTaskAnimationManager.isRecentsAnimationRunning()) {
gestureState.updateRunningTask(mGestureState.getRunningTask()); gestureState.updateRunningTask(mGestureState.getRunningTask());
gestureState.updateLastStartedTaskId(mGestureState.getLastStartedTaskId()); gestureState.updateLastStartedTaskId(mGestureState.getLastStartedTaskId());
gestureState.updatePreviouslyAppearedTaskIds(
mGestureState.getPreviouslyAppearedTaskIds());
} else { } else {
gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0", gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
() -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */))); () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
@@ -267,10 +267,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (!mPassedSlopOnThisGesture && passedSlop) { if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true; mPassedSlopOnThisGesture = true;
} }
// Until passing slop, we don't know what direction we're going, so assume we might
// be quick switching to avoid translating recents away when continuing the gesture.
boolean isLikelyToStartNewTask = !mPassedSlopOnThisGesture
|| horizontalDist > upDist;
if (!mPassedPilferInputSlop) { if (!mPassedPilferInputSlop) {
if (passedSlop) { if (passedSlop) {
@@ -304,6 +300,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
} }
if (mDeviceState.isFullyGesturalNavMode()) { if (mDeviceState.isFullyGesturalNavMode()) {
// Until passing slop, we don't know what direction we're going, so assume
// we're quick switching to avoid translating recents away when continuing
// the gesture.
boolean haveNotPassedSlopOnContinuedGesture =
!mPassedSlopOnThisGesture && mPassedPilferInputSlop;
boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
|| horizontalDist > upDist;
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask); || isLikelyToStartNewTask);
mMotionPauseDetector.addPosition(ev); mMotionPauseDetector.addPosition(ev);
@@ -36,8 +36,6 @@ import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.InputMonitorCompat;
import java.util.function.Predicate;
/** /**
* Input consumer for handling touch on the recents/Launcher activity. * Input consumer for handling touch on the recents/Launcher activity.
*/ */
@@ -50,8 +48,6 @@ public class OverviewInputConsumer<T extends StatefulActivity<?>>
private final InputMonitorCompat mInputMonitor; private final InputMonitorCompat mInputMonitor;
private final int[] mLocationOnScreen = new int[2]; private final int[] mLocationOnScreen = new int[2];
private final boolean mProxyTouch;
private final Predicate<MotionEvent> mEventReceiver;
private final boolean mStartingInActivityBounds; private final boolean mStartingInActivityBounds;
private boolean mTargetHandledTouch; private boolean mTargetHandledTouch;
@@ -64,15 +60,7 @@ public class OverviewInputConsumer<T extends StatefulActivity<?>>
mActivityInterface = gestureState.getActivityInterface(); mActivityInterface = gestureState.getActivityInterface();
mTarget = activity.getDragLayer(); mTarget = activity.getDragLayer();
if (startingInActivityBounds) { mTarget.getLocationOnScreen(mLocationOnScreen);
mEventReceiver = mTarget::dispatchTouchEvent;
mProxyTouch = true;
} else {
// Only proxy touches to controllers if we are starting touch from nav bar.
mEventReceiver = mTarget::proxyTouchEvent;
mTarget.getLocationOnScreen(mLocationOnScreen);
mProxyTouch = mTarget.prepareProxyEventStarting();
}
} }
@Override @Override
@@ -87,10 +75,6 @@ public class OverviewInputConsumer<T extends StatefulActivity<?>>
@Override @Override
public void onMotionEvent(MotionEvent ev) { public void onMotionEvent(MotionEvent ev) {
if (!mProxyTouch) {
return;
}
int flags = ev.getEdgeFlags(); int flags = ev.getEdgeFlags();
if (!mStartingInActivityBounds) { if (!mStartingInActivityBounds) {
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR); ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
@@ -99,7 +83,7 @@ public class OverviewInputConsumer<T extends StatefulActivity<?>>
if (TestProtocol.sDebugTracing) { if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer"); Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer");
} }
boolean handled = mEventReceiver.test(ev); boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]); ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags); ev.setEdgeFlags(flags);
@@ -30,6 +30,7 @@ import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -607,6 +608,17 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
} }
} }
/**
* Whether the Clear All button is hidden or fully visible. Used to determine if center
* displayed page is a task or the Clear All button.
*
* @return True = Clear All button not fully visible, center page is a task. False = Clear All
* button fully visible, center page is Clear All button.
*/
public boolean isClearAllHidden() {
return mClearAllButton.getAlpha() != 1f;
}
@Override @Override
protected void onPageBeginTransition() { protected void onPageBeginTransition() {
super.onPageBeginTransition(); super.onPageBeginTransition();
@@ -616,7 +628,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
@Override @Override
protected void onPageEndTransition() { protected void onPageEndTransition() {
super.onPageEndTransition(); super.onPageEndTransition();
if (getScrollX() == getScrollForPage(getPageNearestToCenterOfScreen())) { if (isClearAllHidden()) {
LayoutUtils.setViewEnabled(mActionsView, true); LayoutUtils.setViewEnabled(mActionsView, true);
} }
if (getNextPage() > 0) { if (getNextPage() > 0) {
@@ -1336,7 +1348,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
endState.logAction, Direction.UP, index, compKey); endState.logAction, Direction.UP, index, compKey);
mActivity.getStatsLogManager().log( mActivity.getStatsLogManager().log(
LAUNCHER_TASK_DISMISS_SWIPE_UP, taskView.buildProto()); LAUNCHER_TASK_DISMISS_SWIPE_UP, taskView.getItemInfo());
} }
} }
@@ -1720,7 +1732,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private void updatePageOffsets() { private void updatePageOffsets() {
float offset = mAdjacentPageOffset * getWidth(); float offset = mAdjacentPageOffset * getWidth();
float modalOffset = mTaskModalness * getWidth(); float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
if (mIsRtl) { if (mIsRtl) {
offset = -offset; offset = -offset;
modalOffset = -modalOffset; modalOffset = -modalOffset;
@@ -1748,6 +1760,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
return Math.max(getWidth(), 1); return Math.max(getWidth(), 1);
} }
/**
* Resets the visuals when exit modal state.
*/
public void resetModalVisuals() {
TaskView taskView = getCurrentPageTaskView();
if (taskView != null) {
taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
}
}
private void updateDeadZoneRects() { private void updateDeadZoneRects() {
// Get the deadzone rect surrounding the clear all button to not dismiss overview to home // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
mClearAllButtonDeadZoneRect.setEmpty(); mClearAllButtonDeadZoneRect.setEmpty();
@@ -1919,7 +1941,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
endState.logAction, Direction.DOWN, indexOfChild(tv), endState.logAction, Direction.DOWN, indexOfChild(tv),
TaskUtils.getLaunchComponentKeyForTask(task.key)); TaskUtils.getLaunchComponentKeyForTask(task.key));
mActivity.getStatsLogManager().log( mActivity.getStatsLogManager().log(
LAUNCHER_TASK_LAUNCH_SWIPE_DOWN, tv.buildProto()); LAUNCHER_TASK_LAUNCH_SWIPE_DOWN, tv.getItemInfo());
} }
} else { } else {
onTaskLaunched(false); onTaskLaunched(false);
@@ -2089,6 +2111,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
return mClearAllButton; return mClearAllButton;
} }
@Override
protected boolean onOverscroll(int amount) {
// overscroll should only be accepted on -1 direction (for clear all button)
if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
return super.onOverscroll(amount);
}
/** /**
* @return How many pixels the running task is offset on the currently laid out dominant axis. * @return How many pixels the running task is offset on the currently laid out dominant axis.
@@ -2182,6 +2210,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
if (getCurrentPageTaskView() != null) { if (getCurrentPageTaskView() != null) {
getCurrentPageTaskView().setModalness(modalness); getCurrentPageTaskView().setModalness(modalness);
} }
// Only show actions view when it's modal for in-place landscape mode.
boolean inPlaceLandscape = !mOrientationState.canLauncherRotate()
&& mOrientationState.getTouchRotation() != ROTATION_0;
mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
LayoutUtils.setViewEnabled(mActionsView, true);
} }
@Nullable @Nullable
@@ -447,6 +447,16 @@ public class TaskThumbnailView extends View implements PluginListener<OverviewSc
return mThumbnailData.thumbnail; return mThumbnailData.thumbnail;
} }
/**
* Returns whether the snapshot is real.
*/
public boolean isRealSnapshot() {
if (mThumbnailData == null) {
return false;
}
return mThumbnailData.isRealSnapshot;
}
/** /**
* Utility class to position the thumbnail in the TaskView * Utility class to position the thumbnail in the TaskView
*/ */
@@ -40,6 +40,7 @@ import android.animation.TimeInterpolator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.app.ActivityOptions; import android.app.ActivityOptions;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.Outline; import android.graphics.Outline;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
@@ -48,7 +49,6 @@ import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.InsetDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Process;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.FloatProperty; import android.util.FloatProperty;
import android.util.Log; import android.util.Log;
@@ -61,13 +61,14 @@ import android.widget.Toast;
import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.testing.TestProtocol;
@@ -213,7 +214,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this), Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
TaskUtils.getLaunchComponentKeyForTask(getTask().key)); TaskUtils.getLaunchComponentKeyForTask(getTask().key));
mActivity.getStatsLogManager().log(LAUNCHER_TASK_LAUNCH_TAP, buildProto()); mActivity.getStatsLogManager().log(LAUNCHER_TASK_LAUNCH_TAP, getItemInfo());
}); });
mCurrentFullscreenParams = new FullscreenDrawParams(context); mCurrentFullscreenParams = new FullscreenDrawParams(context);
@@ -226,14 +227,16 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
/** /**
* Builds proto for logging * Builds proto for logging
*/ */
public LauncherAtom.ItemInfo buildProto() { public WorkspaceItemInfo getItemInfo() {
ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key); ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder(); WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
itemBuilder.setIsWork(componentKey.user != Process.myUserHandle()); dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
itemBuilder.setTask(LauncherAtom.Task.newBuilder() dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
.setComponentName(componentKey.componentName.flattenToShortString()) dummyInfo.user = componentKey.user;
.setIndex(getRecentsView().indexOfChild(this))); dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
return itemBuilder.build(); dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
dummyInfo.screenId = getRecentsView().indexOfChild(this);
return dummyInfo;
} }
@Override @Override
@@ -425,13 +428,16 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
} }
private boolean showTaskMenu(int action) { private boolean showTaskMenu(int action) {
getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); if (!getRecentsView().isClearAllHidden()) {
mMenuView = TaskMenuView.showForTask(this); getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
mActivity.getStatsLogManager().log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS, buildProto()); } else {
UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE, mMenuView = TaskMenuView.showForTask(this);
LauncherLogProto.ItemType.TASK_ICON); mActivity.getStatsLogManager().log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS, getItemInfo());
if (mMenuView != null) { UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener); LauncherLogProto.ItemType.TASK_ICON);
if (mMenuView != null) {
mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
}
} }
return mMenuView != null; return mMenuView != null;
} }
@@ -969,6 +975,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
} }
void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) { void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
if (getRecentsView() == null) {
return;
}
mCurrentFullscreenParams.setProgress( mCurrentFullscreenParams.setProgress(
mFullscreenProgress, mFullscreenProgress,
getRecentsView().getScaleX(), getRecentsView().getScaleX(),
+5 -3
View File
@@ -76,8 +76,8 @@
<!-- Button text to dismiss opt in for fully predicted hotseat --> <!-- Button text to dismiss opt in for fully predicted hotseat -->
<string name="hotseat_edu_dismiss">No thanks</string> <string name="hotseat_edu_dismiss">No thanks</string>
<!-- action shown to turn off predictions after onboarding --> <!-- action shown to toggle predictions after onboarding -->
<string name="hotseat_turn_off">Settings</string> <string name="hotseat_prediction_settings">Settings</string>
<!-- tip shown if user has no items in hotseat to migrate --> <!-- tip shown if user has no items in hotseat to migrate -->
<string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string> <string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
@@ -86,7 +86,9 @@
<!-- tip shown if user declines migration and has some open spots for prediction --> <!-- tip shown if user declines migration and has some open spots for prediction -->
<string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string> <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
<!-- tip shown when user migrates and predictions are enabled in hotseat --> <!-- tip shown when user migrates and predictions are enabled in hotseat -->
<string name="hotsaet_tip_prediction_enabled">App suggestions Enabled</string> <string name="hotsaet_tip_prediction_enabled">App suggestions enabled</string>
<!-- tip shown when hotseat edu is requested while predicions are disabled -->
<string name="hotsaet_tip_prediction_disabled">App suggestions are disabled</string>
<!-- content description for hotseat items --> <!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string> <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
@@ -31,6 +31,7 @@ import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter; import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -75,6 +76,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this); private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
private OverviewActionsView mActionsView; private OverviewActionsView mActionsView;
protected HotseatPredictionController mHotseatPredictionController;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -305,6 +307,13 @@ public abstract class BaseQuickstepLauncher extends Launcher
return mShelfPeekAnim; return mShelfPeekAnim;
} }
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
public void setHintUserWillBeActive() { public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
} }
@@ -32,6 +32,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.states.StateAnimationConfig;
import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.SurfaceControlCompat;
import com.android.systemui.shared.system.TransactionCompat; import com.android.systemui.shared.system.TransactionCompat;
@@ -110,6 +111,10 @@ public class DepthController implements StateHandler<LauncherState> {
} }
private void ensureDependencies() { private void ensureDependencies() {
if (mWallpaperManager == null) {
mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
mWallpaperManager = new WallpaperManagerCompat(mLauncher);
}
if (mLauncher.getRootView() != null && mOnAttachListener == null) { if (mLauncher.getRootView() != null && mOnAttachListener == null) {
mOnAttachListener = new View.OnAttachStateChangeListener() { mOnAttachListener = new View.OnAttachStateChangeListener() {
@Override @Override
@@ -127,11 +132,6 @@ public class DepthController implements StateHandler<LauncherState> {
}; };
mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
} }
if (mWallpaperManager != null) {
return;
}
mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
mWallpaperManager = new WallpaperManagerCompat(mLauncher);
} }
/** /**
@@ -205,7 +205,8 @@ public class DepthController implements StateHandler<LauncherState> {
return; return;
} }
if (mSurface == null || !mSurface.isValid()) { boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
return; return;
} }
mDepth = depthF; mDepth = depthF;
@@ -214,17 +215,20 @@ public class DepthController implements StateHandler<LauncherState> {
if (windowToken != null) { if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
} }
final int blur;
if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { if (supportsBlur) {
// All apps has a solid background. We don't need to draw blurs after it's fully final int blur;
// visible. This will take us out of GPU composition, saving battery and increasing if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) {
// performance. // All apps has a solid background. We don't need to draw blurs after it's fully
blur = 0; // visible. This will take us out of GPU composition, saving battery and increasing
} else { // performance.
blur = (int) (mDepth * mMaxBlurRadius); blur = 0;
} else {
blur = (int) (mDepth * mMaxBlurRadius);
}
new TransactionCompat()
.setBackgroundBlurRadius(mSurface, blur)
.apply();
} }
new TransactionCompat()
.setBackgroundBlurRadius(mSurface, blur)
.apply();
} }
} }
@@ -30,6 +30,8 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/** /**
* Manages the state for an active system gesture, listens for events from the system and Launcher, * Manages the state for an active system gesture, listens for events from the system and Launcher,
@@ -128,6 +130,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
private ActivityManager.RunningTaskInfo mRunningTask; private ActivityManager.RunningTaskInfo mRunningTask;
private GestureEndTarget mEndTarget; private GestureEndTarget mEndTarget;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget; private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
private int mLastStartedTaskId = -1; private int mLastStartedTaskId = -1;
public GestureState(OverviewComponentObserver componentObserver, int gestureId) { public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
@@ -147,6 +150,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
mRunningTask = other.mRunningTask; mRunningTask = other.mRunningTask;
mEndTarget = other.mEndTarget; mEndTarget = other.mEndTarget;
mLastAppearedTaskTarget = other.mLastAppearedTaskTarget; mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
mLastStartedTaskId = other.mLastStartedTaskId; mLastStartedTaskId = other.mLastStartedTaskId;
} }
@@ -234,6 +238,9 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
*/ */
public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) { public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
mLastAppearedTaskTarget = lastAppearedTaskTarget; mLastAppearedTaskTarget = lastAppearedTaskTarget;
if (lastAppearedTaskTarget != null) {
mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
}
} }
/** /**
@@ -243,6 +250,14 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1; return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
} }
public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds;
}
public Set<Integer> getPreviouslyAppearedTaskIds() {
return mPreviouslyAppearedTaskIds;
}
/** /**
* Updates the last task that we started via startActivityFromRecents() during this gesture. * Updates the last task that we started via startActivityFromRecents() during this gesture.
*/ */
@@ -64,6 +64,9 @@ public class StatsLogCompatManager extends StatsLogManager {
private static Context sContext; private static Context sContext;
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0); private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
// LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
// from nano to lite, bake constant to prevent robo test failure.
private static final int DEFAULT_PAGE_INDEX = -2;
private static final int FOLDER_HIERARCHY_OFFSET = 100; private static final int FOLDER_HIERARCHY_OFFSET = 100;
public StatsLogCompatManager(Context context) { public StatsLogCompatManager(Context context) {
@@ -75,7 +78,7 @@ public class StatsLogCompatManager extends StatsLogManager {
*/ */
@Override @Override
public void log(EventEnum event) { public void log(EventEnum event) {
log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance()); log(event, DEFAULT_INSTANCE_ID, (ItemInfo) null);
} }
/** /**
@@ -83,26 +86,62 @@ public class StatsLogCompatManager extends StatsLogManager {
*/ */
@Override @Override
public void log(EventEnum event, InstanceId instanceId) { public void log(EventEnum event, InstanceId instanceId) {
log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance()); log(event, instanceId, (ItemInfo) null);
} }
/** /**
* Logs an event and accompanying {@link ItemInfo}. * Logs an event and accompanying {@link ItemInfo}.
*/ */
@Override @Override
public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo info) { public void log(EventEnum event, @Nullable ItemInfo info) {
log(event, DEFAULT_INSTANCE_ID, info); log(event, DEFAULT_INSTANCE_ID, info);
} }
/**
* Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param atomInfo item typically containing app or task launch related information.
*/
public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) {
LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel,
AllAppsList apps) {
write(event, instanceId, atomInfo, null,
LAUNCHER_UICHANGED__DST_STATE__HOME,
LAUNCHER_UICHANGED__DST_STATE__BACKGROUND);
}
});
}
/**
* Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param atomItemInfo item typically containing app or task launch related information.
*/
@Override
public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo atomItemInfo, int srcState,
int dstState) {
write(event, DEFAULT_INSTANCE_ID,
atomItemInfo == null ? LauncherAtom.ItemInfo.getDefaultInstance() : atomItemInfo,
null,
srcState,
dstState);
}
/** /**
* Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
*/ */
@Override @Override
public void log(EventEnum event, InstanceId instanceId, public void log(EventEnum event, InstanceId instanceId,
@Nullable LauncherAtom.ItemInfo info) { @Nullable ItemInfo info) {
logInternal(event, instanceId, info, logInternal(event, instanceId, info,
LAUNCHER_UICHANGED__DST_STATE__HOME, LAUNCHER_UICHANGED__DST_STATE__HOME,
LAUNCHER_UICHANGED__DST_STATE__BACKGROUND); LAUNCHER_UICHANGED__DST_STATE__BACKGROUND,
DEFAULT_PAGE_INDEX);
} }
/** /**
@@ -124,38 +163,67 @@ public class StatsLogCompatManager extends StatsLogManager {
*/ */
@Override @Override
public void log(EventEnum event, int srcState, int dstState, int pageIndex) { public void log(EventEnum event, int srcState, int dstState, int pageIndex) {
LauncherAtom.ItemInfo info = LauncherAtom.ItemInfo.getDefaultInstance(); logInternal(event, DEFAULT_INSTANCE_ID, null, srcState, dstState, pageIndex);
if (srcState == LAUNCHER_UICHANGED__DST_STATE__HOME
|| dstState == LAUNCHER_UICHANGED__SRC_STATE__HOME) {
info = LauncherAtom.ItemInfo.newBuilder().setContainerInfo(
LauncherAtom.ContainerInfo.newBuilder().setWorkspace(
LauncherAtom.WorkspaceContainer.newBuilder().setPageIndex(pageIndex)
)).build();
}
logInternal(event, DEFAULT_INSTANCE_ID, info, srcState, dstState);
} }
/** /**
* Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
*/ */
private void logInternal(EventEnum event, InstanceId instanceId, private void logInternal(EventEnum event, InstanceId instanceId,
@Nullable LauncherAtom.ItemInfo info, int srcState, int dstState) { @Nullable ItemInfo info, int srcState, int dstState, int pageIndex) {
info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info;
if (IS_VERBOSE) { LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
String name = (event instanceof LauncherEvent) ? ((LauncherEvent) event).name() : new BaseModelUpdateTask() {
event.getId() + ""; @Override
public void execute(LauncherAppState app, BgDataModel dataModel,
AllAppsList apps) {
writeEvent(event, instanceId, info, srcState, dstState, pageIndex,
dataModel.folders);
}
});
}
Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID private static void writeEvent(EventEnum event, InstanceId instanceId,
? String.format("\n%s (State:%s->%s) \n%s", name, getStateString(srcState), @Nullable ItemInfo info, int srcState, int dstState, int pageIndex,
getStateString(dstState), info) IntSparseArrayMap<FolderInfo> folders) {
: String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name, instanceId,
getStateString(srcState), getStateString(dstState), info));
}
if (!Utilities.ATLEAST_R) { if (!Utilities.ATLEAST_R) {
return; return;
} }
LauncherAtom.ItemInfo atomInfo = LauncherAtom.ItemInfo.getDefaultInstance();
if (info != null) {
if (info.container >= 0) {
atomInfo = info.buildProto(folders.get(info.container));
} else {
atomInfo = info.buildProto();
}
} else {
if (srcState == LAUNCHER_UICHANGED__DST_STATE__HOME
|| dstState == LAUNCHER_UICHANGED__SRC_STATE__HOME) {
atomInfo = LauncherAtom.ItemInfo.newBuilder().setContainerInfo(
LauncherAtom.ContainerInfo.newBuilder().setWorkspace(
LauncherAtom.WorkspaceContainer.newBuilder().setPageIndex(pageIndex)
)).build();
}
}
write(event, instanceId, atomInfo, info, srcState, dstState);
}
private static void write(EventEnum event, InstanceId instanceId,
LauncherAtom.ItemInfo atomInfo,
@Nullable ItemInfo info,
int srcState, int dstState) {
if (IS_VERBOSE) {
String name = (event instanceof Enum) ? ((Enum) event).name() :
event.getId() + "";
Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
? String.format("\n%s (State:%s->%s) \n%s\n%s", name, getStateString(srcState),
getStateString(dstState), info, atomInfo)
: String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s\n%s", name,
getStateString(srcState), getStateString(dstState), instanceId, info,
atomInfo));
}
SysUiStatsLog.write( SysUiStatsLog.write(
SysUiStatsLog.LAUNCHER_EVENT, SysUiStatsLog.LAUNCHER_EVENT,
@@ -165,24 +233,24 @@ public class StatsLogCompatManager extends StatsLogManager {
null /* launcher extensions, deprecated */, null /* launcher extensions, deprecated */,
false /* quickstep_enabled, deprecated */, false /* quickstep_enabled, deprecated */,
event.getId() /* event_id */, event.getId() /* event_id */,
info.getItemCase().getNumber() /* target_id */, atomInfo.getItemCase().getNumber() /* target_id */,
instanceId.getId() /* instance_id TODO */, instanceId.getId() /* instance_id TODO */,
0 /* uid TODO */, 0 /* uid TODO */,
getPackageName(info) /* package_name */, getPackageName(atomInfo) /* package_name */,
getComponentName(info) /* component_name */, getComponentName(atomInfo) /* component_name */,
getGridX(info, false) /* grid_x */, getGridX(atomInfo, false) /* grid_x */,
getGridY(info, false) /* grid_y */, getGridY(atomInfo, false) /* grid_y */,
getPageId(info, false) /* page_id */, getPageId(atomInfo, false) /* page_id */,
getGridX(info, true) /* grid_x_parent */, getGridX(atomInfo, true) /* grid_x_parent */,
getGridY(info, true) /* grid_y_parent */, getGridY(atomInfo, true) /* grid_y_parent */,
getPageId(info, true) /* page_id_parent */, getPageId(atomInfo, true) /* page_id_parent */,
getHierarchy(info) /* hierarchy */, getHierarchy(atomInfo) /* hierarchy */,
info.getIsWork() /* is_work_profile */, atomInfo.getIsWork() /* is_work_profile */,
info.getRank() /* rank */, atomInfo.getRank() /* rank */,
info.getFolderIcon().getFromLabelState().getNumber() /* fromState */, atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
info.getFolderIcon().getToLabelState().getNumber() /* toState */, atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */,
info.getFolderIcon().getLabelInfo() /* edittext */, atomInfo.getFolderIcon().getLabelInfo() /* edittext */,
getCardinality(info) /* cardinality */); getCardinality(atomInfo) /* cardinality */);
} }
/** /**
@@ -333,7 +401,7 @@ public class StatsLogCompatManager extends StatsLogManager {
} }
private static String getStateString(int state) { private static String getStateString(int state) {
switch(state) { switch (state) {
case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND: case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND:
return "BACKGROUND"; return "BACKGROUND";
case LAUNCHER_UICHANGED__DST_STATE__HOME: case LAUNCHER_UICHANGED__DST_STATE__HOME:
@@ -31,6 +31,7 @@ import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace; import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.OnboardingPrefs;
@@ -100,6 +101,28 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
}); });
} }
if (!hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
boolean mFromAllApps = false;
@Override
public void onStateTransitionStart(LauncherState toState) {
mFromAllApps = mLauncher.getStateManager().getCurrentStableState() == ALL_APPS;
}
@Override
public void onStateTransitionComplete(LauncherState finalState) {
HotseatPredictionController client = mLauncher.getHotseatPredictionController();
if (mFromAllApps && finalState == NORMAL && client.hasPredictions()) {
if (incrementEventCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
client.showDiscoveryTip();
stateManager.removeStateListener(this);
}
}
}
});
}
if (SysUINavigationMode.getMode(launcher) == NO_BUTTON if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
&& FeatureFlags.ENABLE_ALL_APPS_EDU.get()) { && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
stateManager.addStateListener(new StateListener<LauncherState>() { stateManager.addStateListener(new StateListener<LauncherState>() {
@@ -26,8 +26,8 @@ import static android.view.Surface.ROTATION_90;
import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress; import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -49,6 +49,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.touch.PagedOrientationHandler;
@@ -197,7 +198,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
mPreviousRotation = touchRotation; mPreviousRotation = touchRotation;
if (mLauncherRotation == mTouchRotation || canLauncherRotate()) { if (mLauncherRotation == mTouchRotation || canLauncherRotate()) {
mOrientationHandler = PagedOrientationHandler.HOME_ROTATED; mOrientationHandler = PagedOrientationHandler.PORTRAIT;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "current RecentsOrientedState: " + this); Log.d(TAG, "current RecentsOrientedState: " + this);
} }
@@ -526,4 +527,15 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
+ " mFlags=" + mFlags + " mFlags=" + mFlags
+ "]"; + "]";
} }
/**
* Returns the device profile based on expected launcher rotation
*/
public DeviceProfile getLauncherDeviceProfile() {
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
// TODO also check the natural orientation is landscape or portrait
return (mLauncherRotation == ROTATION_90 || mLauncherRotation == ROTATION_270)
? idp.landscapeProfile
: idp.portraitProfile;
}
} }
@@ -23,6 +23,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
@@ -187,6 +188,7 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset; mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
} }
updateColors(); updateColors();
updateSysUiColors();
updateDragHandleAlpha(); updateDragHandleAlpha();
invalidate(); invalidate();
} }
@@ -240,6 +242,18 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
} }
} }
@Override
protected void updateSysUiColors() {
// Use a light system UI (dark icons) if all apps is behind at least half of the
// status bar.
boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f;
if (forceChange) {
mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
}
}
@Override @Override
protected boolean shouldDragHandleBeVisible() { protected boolean shouldDragHandleBeVisible() {
boolean needsAllAppsEdu = mIsTwoZoneSwipeModel boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
@@ -37,7 +37,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.app.Instrumentation; import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@@ -58,10 +57,8 @@ import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.util.Wait; import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureRewriterRule; import com.android.launcher3.util.rule.FailureRewriterRule;
import com.android.launcher3.util.rule.FailureWatcher; import com.android.launcher3.util.rule.FailureWatcher;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsView;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.RuleChain; import org.junit.rules.RuleChain;
@@ -95,7 +92,8 @@ public class FallbackRecentsTest {
mDevice = UiDevice.getInstance(instrumentation); mDevice = UiDevice.getInstance(instrumentation);
mDevice.setOrientationNatural(); mDevice.setOrientationNatural();
mLauncher = new LauncherInstrumentation(); mLauncher = new LauncherInstrumentation();
mLauncher.enableCheckEventsForSuccessfulGestures(); // b/143488140
//mLauncher.enableCheckEventsForSuccessfulGestures();
if (TestHelpers.isInLauncherProcess()) { if (TestHelpers.isInLauncherProcess()) {
Utilities.enableRunningInTestHarnessForTests(); Utilities.enableRunningInTestHarnessForTests();
@@ -132,9 +130,9 @@ public class FallbackRecentsTest {
} }
} }
@NavigationModeSwitch // b/143488140
//@NavigationModeSwitch
@Test @Test
@Ignore // b/143488140
public void goToOverviewFromHome() { public void goToOverviewFromHome() {
mDevice.pressHome(); mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -143,9 +141,9 @@ public class FallbackRecentsTest {
mLauncher.getBackground().switchToOverview(); mLauncher.getBackground().switchToOverview();
} }
@NavigationModeSwitch // b/143488140
//@NavigationModeSwitch
@Test @Test
@Ignore // b/143488140
public void goToOverviewFromApp() { public void goToOverviewFromApp() {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -178,9 +176,9 @@ public class FallbackRecentsTest {
return mLauncher.getBackground().switchToOverview(); return mLauncher.getBackground().switchToOverview();
} }
@NavigationModeSwitch // b/143488140
//@NavigationModeSwitch
@Test @Test
@Ignore // b/143488140
public void testOverview() { public void testOverview() {
startAppFast(getAppPackageName()); startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -45,6 +45,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
// b/143488140
mLauncher.pressHome();
// Start an activity where the gestures start. // Start an activity where the gestures start.
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
} }
@@ -98,7 +100,6 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
// The test action. // The test action.
mLauncher.getBackground().switchToOverview(); mLauncher.getBackground().switchToOverview();
} }
// Workaround for b/157099707
mLauncher.pressHome(); mLauncher.pressHome();
} }
} }
+3 -2
View File
@@ -34,6 +34,8 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:gravity="center" android:gravity="center"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:textColor="@android:color/white" android:textColor="@android:color/white"
@@ -58,6 +60,5 @@
android:elevation="2dp" android:elevation="2dp"
android:layout_width="10dp" android:layout_width="10dp"
android:layout_height="8dp" android:layout_height="8dp"
android:layout_marginTop="-2dp" android:layout_marginTop="-2dp"/>
android:layout_gravity="center_horizontal"/>
</merge> </merge>
+4 -2
View File
@@ -172,8 +172,10 @@
<string name="folder_closed">Folder closed</string> <string name="folder_closed">Folder closed</string>
<!-- Folder renamed format --> <!-- Folder renamed format -->
<string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string> <string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string>
<!-- Folder name format --> <!-- Folder name format when folder has less than 4 items -->
<string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string> <string name="folder_name_format_exact">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> items</string>
<!-- Folder name format when folder has 4 or more items shown in preview-->
<string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
<!-- Strings for the customization mode --> <!-- Strings for the customization mode -->
<!-- Text for widget add button --> <!-- Text for widget add button -->
@@ -89,6 +89,22 @@ public class DefaultLayoutProviderTest {
assertEquals(3, ((FolderInfo) info).contents.size()); assertEquals(3, ((FolderInfo) info).contents.size());
} }
@Test
public void testCustomProfileLoaded_with_folder_custom_title() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder")
.addApp(TEST_PACKAGE, TEST_PACKAGE)
.addApp(TEST_PACKAGE, TEST_PACKAGE)
.addApp(TEST_PACKAGE, TEST_PACKAGE)
.build());
// Verify folder
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).contents.size());
assertEquals("CustomFolder", info.title.toString());
}
@Test @Test
public void testCustomProfileLoaded_with_widget() throws Exception { public void testCustomProfileLoaded_with_widget() throws Exception {
String pendingAppPkg = "com.test.pending"; String pendingAppPkg = "com.test.pending";
@@ -47,6 +47,7 @@ public class LauncherLayoutBuilder {
private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_CLASS_NAME = "className"; private static final String ATTR_CLASS_NAME = "className";
private static final String ATTR_TITLE = "title"; private static final String ATTR_TITLE = "title";
private static final String ATTR_TITLE_TEXT = "titleText";
private static final String ATTR_SCREEN = "screen"; private static final String ATTR_SCREEN = "screen";
// x and y can be specified as negative integers, in which case -1 represents the // x and y can be specified as negative integers, in which case -1 represents the
@@ -145,8 +146,17 @@ public class LauncherLayoutBuilder {
} }
public FolderBuilder putFolder(int titleResId) { public FolderBuilder putFolder(int titleResId) {
FolderBuilder folderBuilder = new FolderBuilder();
items.put(ATTR_TITLE, Integer.toString(titleResId)); items.put(ATTR_TITLE, Integer.toString(titleResId));
return putFolder();
}
public FolderBuilder putFolder(String title) {
items.put(ATTR_TITLE_TEXT, title);
return putFolder();
}
private FolderBuilder putFolder() {
FolderBuilder folderBuilder = new FolderBuilder();
items.put(ATTR_CHILDREN, folderBuilder.mChildren); items.put(ATTR_CHILDREN, folderBuilder.mChildren);
mNodes.add(Pair.create(TAG_FOLDER, items)); mNodes.add(Pair.create(TAG_FOLDER, items));
return folderBuilder; return folderBuilder;
@@ -174,7 +174,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second); targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
if (mIsOpen) { if (mIsOpen) {
performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); getAccessibilityInitialFocusView().performAccessibilityAction(
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
} }
ActivityContext.lookupContext(getContext()).getDragLayer() ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED); .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
@@ -184,6 +185,11 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
return null; return null;
} }
/** Returns the View that Accessibility services should focus on first. */
protected View getAccessibilityInitialFocusView() {
return this;
}
/** /**
* Returns a view matching FloatingViewType * Returns a view matching FloatingViewType
*/ */
@@ -132,6 +132,7 @@ public class AutoInstallsLayout {
private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_CLASS_NAME = "className"; private static final String ATTR_CLASS_NAME = "className";
private static final String ATTR_TITLE = "title"; private static final String ATTR_TITLE = "title";
private static final String ATTR_TITLE_TEXT = "titleText";
private static final String ATTR_SCREEN = "screen"; private static final String ATTR_SCREEN = "screen";
// x and y can be specified as negative integers, in which case -1 represents the // x and y can be specified as negative integers, in which case -1 represents the
@@ -585,7 +586,8 @@ public class AutoInstallsLayout {
if (titleResId != 0) { if (titleResId != 0) {
title = mSourceRes.getString(titleResId); title = mSourceRes.getString(titleResId);
} else { } else {
title = ""; String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT);
title = TextUtils.isEmpty(titleText) ? "" : titleText;
} }
mValues.put(Favorites.TITLE, title); mValues.put(Favorites.TITLE, title);
@@ -43,6 +43,8 @@ import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -188,7 +190,8 @@ public abstract class BaseDraggingActivity extends BaseActivity
} }
getUserEventDispatcher().logAppLaunch(v, intent, user); getUserEventDispatcher().logAppLaunch(v, intent, user);
if (item != null) { if (item != null) {
getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, item.buildProto()); InstanceId instanceId = new InstanceIdSequence().newInstanceId();
logAppLaunch(item, instanceId);
} }
return true; return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) { } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
@@ -198,6 +201,10 @@ public abstract class BaseDraggingActivity extends BaseActivity
return false; return false;
} }
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, instanceId, info);
}
private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
@Nullable String sourceContainer) { @Nullable String sourceContainer) {
try { try {
@@ -163,6 +163,7 @@ public class LauncherSettings {
public static final int CONTAINER_SEARCH_RESULTS = -106; public static final int CONTAINER_SEARCH_RESULTS = -106;
public static final int CONTAINER_SHORTCUTS = -107; public static final int CONTAINER_SHORTCUTS = -107;
public static final int CONTAINER_SETTINGS = -108; public static final int CONTAINER_SETTINGS = -108;
public static final int CONTAINER_TASKSWITCHER = -109;
public static final String containerToString(int container) { public static final String containerToString(int container) {
switch (container) { switch (container) {
@@ -249,6 +250,12 @@ public class LauncherSettings {
*/ */
public static final int ITEM_TYPE_DEEP_SHORTCUT = 6; public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
/**
* Type of the item is recents task.
* TODO(hyunyoungs): move constants not related to Favorites DB to a better location.
*/
public static final int ITEM_TYPE_TASK = 7;
/** /**
* The appWidgetId of the widget * The appWidgetId of the widget
* *
+1
View File
@@ -606,6 +606,7 @@ public final class Utilities {
outObj[0] = activityInfo; outObj[0] = activityInfo;
return activityInfo.getFullResIcon(appState.getIconCache()); return activityInfo.getFullResIcon(appState.getIconCache());
} }
if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info) List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
.buildRequest(launcher) .buildRequest(launcher)
.query(ShortcutRequest.ALL); .query(ShortcutRequest.ALL);
+6 -9
View File
@@ -418,10 +418,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED, LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED,
dragObject.logInstanceId, dragObject.logInstanceId,
dragObject.dragSource instanceof Folder dragObject.originalDragInfo
? dragObject.originalDragInfo
.buildProto(((Folder) dragObject.dragSource).mInfo)
: dragObject.originalDragInfo.buildProto()
); );
} }
@@ -1652,7 +1649,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED, LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED,
d.logInstanceId, d.logInstanceId,
destInfo.buildProto(null)); destInfo);
FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0], FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
targetCell[1]); targetCell[1]);
destInfo.cellX = -1; destInfo.cellX = -1;
@@ -1693,7 +1690,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
d.logInstanceId, d.logInstanceId,
fi.mInfo.buildProto(null)); fi.mInfo);
fi.onDrop(d, false /* itemReturnedOnFailedDrop */); fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
// if the drag started here, we need to remove it from the workspace // if the drag started here, we need to remove it from the workspace
@@ -1899,7 +1896,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
d.logInstanceId, d.logInstanceId,
d.dragInfo.buildProto(null)); d.dragInfo);
} }
if (d.stateAnnouncer != null && !droppedOnOriginalCell) { if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2440,7 +2437,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
d.logInstanceId, d.logInstanceId,
d.dragInfo.buildProto(null)); d.dragInfo);
} }
}; };
boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
@@ -2532,7 +2529,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mStatsLogManager.log( mStatsLogManager.log(
LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
d.logInstanceId, d.logInstanceId,
d.dragInfo.buildProto(null)); d.dragInfo);
} }
} }
@@ -14,7 +14,6 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FA
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -37,7 +36,6 @@ import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView; import com.android.launcher3.views.ScrimView;
import com.android.systemui.plugins.AllAppsSearchPlugin; import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginListener;
@@ -75,7 +73,6 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
private ScrimView mScrimView; private ScrimView mScrimView;
private final Launcher mLauncher; private final Launcher mLauncher;
private final boolean mIsDarkTheme;
private boolean mIsVerticalLayout; private boolean mIsVerticalLayout;
// Animation in this class is controlled by a single variable {@link mProgress}. // Animation in this class is controlled by a single variable {@link mProgress}.
@@ -98,7 +95,6 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
mShiftRange = mLauncher.getDeviceProfile().heightPx; mShiftRange = mLauncher.getDeviceProfile().heightPx;
mProgress = 1f; mProgress = 1f;
mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout(); mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
mLauncher.addOnDeviceProfileChangeListener(this); mLauncher.addOnDeviceProfileChangeListener(this);
} }
@@ -137,16 +133,6 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
if (mPlugin != null) { if (mPlugin != null) {
mPlugin.setProgress(progress); mPlugin.setProgress(progress);
} }
// Use a light system UI (dark icons) if all apps is behind at least half of the
// status bar.
boolean forceChange = Math.min(shiftCurrent, mScrimView.getVisualTop())
<= mLauncher.getDeviceProfile().getInsets().top / 2f;
if (forceChange) {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
}
} }
public float getProgress() { public float getProgress() {
@@ -35,6 +35,7 @@ public class FlingSpringAnim {
private final FlingAnimation mFlingAnim; private final FlingAnimation mFlingAnim;
private SpringAnimation mSpringAnim; private SpringAnimation mSpringAnim;
private final boolean mSkipFlingAnim;
private float mTargetPosition; private float mTargetPosition;
@@ -57,6 +58,10 @@ public class FlingSpringAnim {
.setMaxValue(maxValue); .setMaxValue(maxValue);
mTargetPosition = targetPosition; mTargetPosition = targetPosition;
// We are already past the fling target, so skip it to avoid losing a frame of the spring.
mSkipFlingAnim = startPosition <= minValue && startVelocity < 0
|| startPosition >= maxValue && startVelocity > 0;
mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> { mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
mSpringAnim = new SpringAnimation(object, property) mSpringAnim = new SpringAnimation(object, property)
.setStartValue(value) .setStartValue(value)
@@ -84,6 +89,9 @@ public class FlingSpringAnim {
public void start() { public void start() {
mFlingAnim.start(); mFlingAnim.start();
if (mSkipFlingAnim) {
mFlingAnim.cancel();
}
} }
public void end() { public void end() {
+7 -2
View File
@@ -745,6 +745,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
: getContext().getString(R.string.folder_closed)); : getContext().getString(R.string.folder_closed));
} }
@Override
protected View getAccessibilityInitialFocusView() {
return mContent.getFirstItem();
}
private void closeComplete(boolean wasAnimated) { private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations. // TODO: Clear all active animations.
DragLayer parent = (DragLayer) getParent(); DragLayer parent = (DragLayer) getParent();
@@ -1333,7 +1338,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
d.stateAnnouncer.completeAction(R.string.item_moved); d.stateAnnouncer.completeAction(R.string.item_moved);
} }
mStatsLogManager mStatsLogManager
.log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo)); .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo);
} }
// This is used so the item doesn't immediately appear in the folder when added. In one case // This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1438,7 +1443,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
if (hasFocus) { if (hasFocus) {
startEditingFolderName(); startEditingFolderName();
} else { } else {
mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto()); mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo);
logFolderLabelState(); logFolderLabelState();
mFolderName.dispatchBackKey(); mFolderName.dispatchBackKey();
} }
@@ -201,8 +201,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
icon.mActivity = activity; icon.mActivity = activity;
icon.mDotRenderer = grid.mDotRendererWorkSpace; icon.mDotRenderer = grid.mDotRendererWorkSpace;
icon.setContentDescription( icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title));
group.getContext().getString(R.string.folder_name_format, folderInfo.title));
// Keep the notification dot up to date with the sum of all the content's dots. // Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo(); FolderDotInfo folderDotInfo = new FolderDotInfo();
@@ -450,7 +449,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
} }
mInfo.setTitle(nameInfos[0].getLabel()); mInfo.setTitle(nameInfos[0].getLabel());
StatsLogManager.newInstance(getContext()) StatsLogManager.newInstance(getContext())
.log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto()); .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo);
onTitleChanged(mInfo.title); onTitleChanged(mInfo.title);
mFolder.mFolderName.setText(mInfo.title); mFolder.mFolderName.setText(mInfo.title);
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -665,6 +664,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item)); mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot(); boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted); updateDotScale(wasDotted, isDotted);
setContentDescription(getAccessiblityTitle(mInfo.title));
invalidate(); invalidate();
requestLayout(); requestLayout();
} }
@@ -675,13 +675,14 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item)); mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot(); boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted); updateDotScale(wasDotted, isDotted);
setContentDescription(getAccessiblityTitle(mInfo.title));
invalidate(); invalidate();
requestLayout(); requestLayout();
} }
public void onTitleChanged(CharSequence title) { public void onTitleChanged(CharSequence title) {
mFolderName.setText(title); mFolderName.setText(title);
setContentDescription(getContext().getString(R.string.folder_name_format, title)); setContentDescription(getAccessiblityTitle(title));
} }
@Override @Override
@@ -775,4 +776,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
public void getWorkspaceVisualDragBounds(Rect bounds) { public void getWorkspaceVisualDragBounds(Rect bounds) {
getPreviewBounds(bounds); getPreviewBounds(bounds);
} }
/**
* Returns a formatted accessibility title for folder
*/
public String getAccessiblityTitle(CharSequence title) {
int size = mInfo.contents.size();
if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
return getContext().getString(R.string.folder_name_format_exact, title, size);
} else {
return getContext().getString(R.string.folder_name_format_overflow, title,
MAX_NUM_ITEMS_IN_PREVIEW);
}
}
} }
@@ -70,8 +70,11 @@ public class FolderNameEditText extends ExtendedEditText {
for (int i = 0; i < cnt; i++) { for (int i = 0; i < cnt; i++) {
cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
} }
post(() -> getContext().getSystemService(InputMethodManager.class) // post it to future frame so that onSelectionChanged, onFocusChanged, all other
.displayCompletions(this, cInfo)); // TextView flag change and IME animation has settled. Ideally, there should be IMM
// callback to notify when the IME animation and state handling is finished.
postDelayed(() -> getContext().getSystemService(InputMethodManager.class)
.displayCompletions(this, cInfo), 40 /* 2~3 frame delay */);
} }
/** /**
@@ -44,6 +44,13 @@ public class InstanceIdSequence {
mInstanceIdMax = min(max(1, instanceIdMax), InstanceId.INSTANCE_ID_MAX); mInstanceIdMax = min(max(1, instanceIdMax), InstanceId.INSTANCE_ID_MAX);
} }
/**
* Constructs a sequence with identifiers [1, InstanceId.INSTANCE_ID_MAX].
*/
public InstanceIdSequence() {
this(InstanceId.INSTANCE_ID_MAX);
}
/** /**
* Gets the next instance from the sequence. Safe for concurrent use. * Gets the next instance from the sequence. Safe for concurrent use.
* @return new InstanceId * @return new InstanceId
@@ -21,8 +21,9 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherState;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom.ItemInfo; import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.util.ResourceBasedOverride;
/** /**
@@ -127,9 +128,29 @@ public class StatsLogManager implements ResourceBasedOverride {
LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522), LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522),
@UiEvent(doc = "User is shown All Apps education view.") @UiEvent(doc = "User is shown All Apps education view.")
LAUNCHER_ALL_APPS_EDU_SHOWN(523); LAUNCHER_ALL_APPS_EDU_SHOWN(523),
// ADD MORE
@UiEvent(doc = "User opened a folder.")
LAUNCHER_FOLDER_OPEN(551),
@UiEvent(doc = "Hotseat education half sheet seen")
LAUNCHER_HOTSEAT_EDU_SEEN(479),
@UiEvent(doc = "Hotseat migration accepted")
LAUNCHER_HOTSEAT_EDU_ACCEPT(480),
@UiEvent(doc = "Hotseat migration denied")
LAUNCHER_HOTSEAT_EDU_DENY(481),
@UiEvent(doc = "Hotseat education tip shown")
LAUNCHER_HOTSEAT_EDU_ONLY_TIP(482),
@UiEvent(doc = "App launch ranking logged for all apps predictions")
LAUNCHER_ALL_APPS_RANKED(552),
@UiEvent(doc = "App launch ranking logged for hotseat predictions)")
LAUNCHER_HOTSEAT_RANKED(553);
// ADD MORE
private final int mId; private final int mId;
LauncherEvent(int id) { LauncherEvent(int id) {
@@ -177,25 +198,56 @@ public class StatsLogManager implements ResourceBasedOverride {
} }
/** /**
* Logs a {@link EventEnum}. * Logs an event.
*
* @param event an enum implementing EventEnum interface.
*/ */
public void log(EventEnum event) { public void log(EventEnum event) {
} }
/** /**
* Logs an event and accompanying {@link InstanceId}. * Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param instanceId an identifier obtained from an InstanceIdSequence.
*/ */
public void log(EventEnum event, InstanceId instanceId) { public void log(EventEnum event, InstanceId instanceId) {
} }
/** /**
* Logs an event and accompanying {@link ItemInfo}. * Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param itemInfo item typically containing app or task launch related information.
*/ */
public void log(EventEnum event, @Nullable ItemInfo itemInfo) { public void log(EventEnum event, @Nullable ItemInfo itemInfo) {
} }
/** /**
* Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. * Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param atomInfo item typically containing app or task launch related information.
*/
public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) {
}
/**
* Logs an event and accompanying {@link LauncherState}s.
*
* @param event an enum implementing EventEnum interface.
* @param launcherAtomItemInfo item typically containing app or task launch related information.
*/
public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo launcherAtomItemInfo,
int srcState, int dstState) {
}
/**
* Logs an event.
*
* @param event an enum implementing EventEnum interface.
* @param instanceId an identifier obtained from an InstanceIdSequence.
* @param itemInfo item typically containing app or task launch related information.
*/ */
public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) { public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) {
} }
@@ -204,7 +256,7 @@ public class StatsLogManager implements ResourceBasedOverride {
* Log an event with ranked-choice information along with package. Does nothing if event.getId() * Log an event with ranked-choice information along with package. Does nothing if event.getId()
* <= 0. * <= 0.
* *
* @param rankingEvent an enum implementing UiEventEnum interface. * @param rankingEvent an enum implementing EventEnum interface.
* @param instanceId An identifier obtained from an InstanceIdSequence. * @param instanceId An identifier obtained from an InstanceIdSequence.
* @param packageName the package name of the relevant app, if known (null otherwise). * @param packageName the package name of the relevant app, if known (null otherwise).
* @param position the position picked. * @param position the position picked.
@@ -48,7 +48,6 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DropTarget; import com.android.launcher3.DropTarget;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.LauncherLogProto; import com.android.launcher3.userevent.LauncherLogProto;
@@ -143,14 +142,6 @@ public class UserEventDispatcher implements ResourceBasedOverride {
fillIntentInfo(itemTarget, intent, userHandle); fillIntentInfo(itemTarget, intent, userHandle);
} }
LauncherEvent event = newLauncherEvent(action, targets); LauncherEvent event = newLauncherEvent(action, targets);
ItemInfo info = v == null ? null : (ItemInfo) v.getTag();
if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
final String pkg = info.getTargetComponent() != null
? info.getTargetComponent().getPackageName() : "unknown";
FileLog.d(TAG, "appLaunch: packageName:" + pkg
+ ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
userHandle)) + ",launchLocation:" + info.container);
}
dispatchUserEvent(event, intent); dispatchUserEvent(event, intent);
mAppOrTaskLaunch = true; mAppOrTaskLaunch = true;
} }
@@ -59,7 +59,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -248,12 +247,25 @@ public class GridSizeMigrationTaskV2 {
/** Return what's in the src but not in the dest */ /** Return what's in the src but not in the dest */
private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) { private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
Set<String> destSet = dest.parallelStream().map(DbEntry::getIntentStr).collect( Set<String> destIntentSet = new HashSet<>();
Collectors.toSet()); Set<Set<String>> destFolderIntentSet = new HashSet<>();
for (DbEntry entry : dest) {
if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
destFolderIntentSet.add(entry.mFolderItems.keySet());
} else {
destIntentSet.add(entry.mIntent);
}
}
List<DbEntry> diff = new ArrayList<>(); List<DbEntry> diff = new ArrayList<>();
for (DbEntry entry : src) { for (DbEntry entry : src) {
if (!destSet.contains(entry.mIntent)) { if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff.add(entry); if (!destFolderIntentSet.contains(entry.mFolderItems.keySet())) {
diff.add(entry);
}
} else {
if (!destIntentSet.contains(entry.mIntent)) {
diff.add(entry);
}
} }
} }
return diff; return diff;
@@ -24,11 +24,13 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICT
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import android.content.ComponentName; import android.content.ComponentName;
@@ -49,6 +51,7 @@ import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
import com.android.launcher3.logger.LauncherAtom.SearchResultContainer; import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import com.android.launcher3.logger.LauncherAtom.SettingsContainer; import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer; import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.ContentWriter;
import java.util.Optional; import java.util.Optional;
@@ -298,6 +301,12 @@ public class ItemInfo {
.setSpanX(spanX) .setSpanX(spanX)
.setSpanY(spanY)); .setSpanY(spanY));
break; break;
case ITEM_TYPE_TASK:
itemBuilder
.setTask(LauncherAtom.Task.newBuilder()
.setComponentName(getTargetComponent().flattenToShortString())
.setIndex(screenId));
break;
default: default:
break; break;
} }
@@ -378,6 +387,11 @@ public class ItemInfo {
return ContainerInfo.newBuilder() return ContainerInfo.newBuilder()
.setSettingsContainer(SettingsContainer.getDefaultInstance()) .setSettingsContainer(SettingsContainer.getDefaultInstance())
.build(); .build();
case CONTAINER_TASKSWITCHER:
return ContainerInfo.newBuilder()
.setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
.build();
} }
return ContainerInfo.getDefaultInstance(); return ContainerInfo.getDefaultInstance();
} }
@@ -108,7 +108,7 @@ public class NotificationInfo implements View.OnClickListener {
intent.send(null, 0, null, null, null, null, activityOptions); intent.send(null, 0, null, null, null, null, activityOptions);
launcher.getUserEventDispatcher().logNotificationLaunch(view, intent); launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
launcher.getStatsLogManager() launcher.getStatsLogManager()
.log(LAUNCHER_NOTIFICATION_LAUNCH_TAP, mItemInfo.buildProto()); .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP, mItemInfo);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -389,6 +389,11 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
return Pair.create(this, ""); return Pair.create(this, "");
} }
@Override
protected View getAccessibilityInitialFocusView() {
return getChildCount() > 0 ? getChildAt(0) : this;
}
private void animateOpen() { private void animateOpen() {
setVisibility(View.VISIBLE); setVisibility(View.VISIBLE);
@@ -78,7 +78,7 @@ public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
public void onClick(View view) { public void onClick(View view) {
AbstractFloatingView.closeAllOpenViews(mTarget); AbstractFloatingView.closeAllOpenViews(mTarget);
mTarget.getStatsLogManager() mTarget.getStatsLogManager()
.log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP, mItemInfo.buildProto()); .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP, mItemInfo);
final String actionIdentity = mAction.getTitle() + ", " final String actionIdentity = mAction.getTitle() + ", "
+ mItemInfo.getTargetComponent().getPackageName(); + mItemInfo.getTargetComponent().getPackageName();
@@ -119,9 +119,7 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
widgetsBottomSheet.populateAndShow(mItemInfo); widgetsBottomSheet.populateAndShow(mItemInfo);
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.WIDGETS_BUTTON, view); ControlType.WIDGETS_BUTTON, view);
// TODO(thiruram): Fix missing container info when item is inside folder. mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP, mItemInfo);
mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP,
mItemInfo.buildProto());
} }
} }
@@ -142,9 +140,8 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.APPINFO_TARGET, view); ControlType.APPINFO_TARGET, view);
// TODO(thiruram): Fix missing container info when item is inside folder.
mTarget.getStatsLogManager() mTarget.getStatsLogManager()
.log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP, mItemInfo.buildProto()); .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP, mItemInfo);
} }
} }
@@ -310,6 +310,9 @@ public abstract class AbstractStateChangeTouchController
} }
protected void updateProgress(float fraction) { protected void updateProgress(float fraction) {
if (mCurrentAnimation == null) {
return;
}
mCurrentAnimation.setPlayFraction(fraction); mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) { if (mAtomicComponentsController != null) {
// Make sure we don't divide by 0, and have at least a small runway. // Make sure we don't divide by 0, and have at least a small runway.
@@ -1,55 +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.touch;
import android.graphics.RectF;
import android.view.Surface;
import android.widget.LinearLayout;
public class HomeRotatedPageHandler extends PortraitPagedViewHandler {
@Override
public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (launcherRotation == Surface.ROTATION_0) {
super.offsetTaskRect(rect, value, displayRotation, launcherRotation);
} else if (launcherRotation == Surface.ROTATION_90) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, value);
} else if (displayRotation == Surface.ROTATION_90) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_180) {
rect.offset(-value, 0);
} else {
rect.offset(-value, 0);
}
} else if (launcherRotation == Surface.ROTATION_270) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, -value);
} else if (displayRotation == Surface.ROTATION_90) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_180) {
rect.offset(0, -value);
} else {
rect.offset(value, 0);
}
} // TODO (b/149609488) handle 180 case as well
}
@Override
public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
return taskMenuLayout.getOrientation();
}
}
@@ -17,6 +17,7 @@ package com.android.launcher3.touch;
import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET; import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET; import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS; import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
@@ -45,6 +46,7 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfo;
@@ -111,6 +113,7 @@ public class ItemClickHandler {
if (!folder.isOpen() && !folder.isDestroyed()) { if (!folder.isOpen() && !folder.isDestroyed()) {
// Open the requested folder // Open the requested folder
folder.animateOpen(); folder.animateOpen();
StatsLogManager.newInstance(v.getContext()).log(LAUNCHER_FOLDER_OPEN, folder.mInfo);
} }
} }
@@ -180,19 +180,6 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
return Surface.ROTATION_90; return Surface.ROTATION_90;
} }
@Override
public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, value);
} else if (displayRotation == Surface.ROTATION_90) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_180) {
rect.offset(0, -value);
} else {
rect.offset(-value, 0);
}
}
@Override @Override
public int getChildStart(View view) { public int getChildStart(View view) {
return view.getTop(); return view.getTop();
@@ -43,7 +43,6 @@ public interface PagedOrientationHandler {
PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler(); PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler(); PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler(); PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
PagedOrientationHandler HOME_ROTATED = new HomeRotatedPageHandler();
interface Int2DAction<T> { interface Int2DAction<T> {
void call(T target, int x, int y); void call(T target, int x, int y);
@@ -82,7 +81,6 @@ public interface PagedOrientationHandler {
boolean getRecentsRtlSetting(Resources resources); boolean getRecentsRtlSetting(Resources resources);
float getDegreesRotated(); float getDegreesRotated();
int getRotation(); int getRotation();
void offsetTaskRect(RectF rect, float value, int delta, int launcherRotation);
int getPrimaryValue(int x, int y); int getPrimaryValue(int x, int y);
int getSecondaryValue(int x, int y); int getSecondaryValue(int x, int y);
void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll); void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
@@ -178,19 +178,6 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
return Surface.ROTATION_0; return Surface.ROTATION_0;
} }
@Override
public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_90) {
rect.offset(0, -value);
} else if (displayRotation == Surface.ROTATION_180) {
rect.offset(-value, 0);
} else {
rect.offset(0, value);
}
}
@Override @Override
public int getChildStart(View view) { public int getChildStart(View view) {
return view.getLeft(); return view.getLeft();
@@ -250,7 +237,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
@Override @Override
public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) { public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
return LinearLayout.VERTICAL; return taskMenuLayout.getOrientation();
} }
@Override @Override
@@ -18,7 +18,6 @@ package com.android.launcher3.touch;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.RectF;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
@@ -41,19 +40,6 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
return Utilities.isRtl(resources); return Utilities.isRtl(resources);
} }
@Override
public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, value);
} else if (displayRotation == Surface.ROTATION_90) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_180) {
rect.offset(0, -value);
} else {
rect.offset(-value, 0);
}
}
@Override @Override
public float getDegreesRotated() { public float getDegreesRotated() {
return 270; return 270;
@@ -37,13 +37,14 @@ public class OnboardingPrefs<T extends Launcher> {
public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count"; public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count"; public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
public static final String ALL_APPS_COUNT = "launcher.all_apps_count"; public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
/** /**
* Events that either have happened or have not (booleans). * Events that either have happened or have not (booleans).
*/ */
@StringDef(value = { @StringDef(value = {
HOME_BOUNCE_SEEN, HOME_BOUNCE_SEEN,
SHELF_BOUNCE_SEEN, SHELF_BOUNCE_SEEN
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {} public @interface EventBoolKey {}
@@ -55,6 +56,7 @@ public class OnboardingPrefs<T extends Launcher> {
HOME_BOUNCE_COUNT, HOME_BOUNCE_COUNT,
SHELF_BOUNCE_COUNT, SHELF_BOUNCE_COUNT,
ALL_APPS_COUNT, ALL_APPS_COUNT,
HOTSEAT_DISCOVERY_TIP_COUNT
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface EventCountKey {} public @interface EventCountKey {}
@@ -65,6 +67,7 @@ public class OnboardingPrefs<T extends Launcher> {
maxCounts.put(HOME_BOUNCE_COUNT, 3); maxCounts.put(HOME_BOUNCE_COUNT, 3);
maxCounts.put(SHELF_BOUNCE_COUNT, 3); maxCounts.put(SHELF_BOUNCE_COUNT, 3);
maxCounts.put(ALL_APPS_COUNT, 5); maxCounts.put(ALL_APPS_COUNT, 5);
maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
MAX_COUNTS = Collections.unmodifiableMap(maxCounts); MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
} }
@@ -30,7 +30,7 @@ public class SystemUiController {
// Various UI states in increasing order of priority // Various UI states in increasing order of priority
public static final int UI_STATE_BASE_WINDOW = 0; public static final int UI_STATE_BASE_WINDOW = 0;
public static final int UI_STATE_ALL_APPS = 1; public static final int UI_STATE_SCRIM_VIEW = 1;
public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2; public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
public static final int UI_STATE_OVERVIEW = 3; public static final int UI_STATE_OVERVIEW = 3;
@@ -61,25 +61,38 @@ public class SystemUiController {
// Apply the state flags in priority order // Apply the state flags in priority order
int newFlags = oldFlags; int newFlags = oldFlags;
for (int stateFlag : mStates) { for (int stateFlag : mStates) {
if (Utilities.ATLEAST_OREO) { newFlags = getSysUiVisibilityFlags(stateFlag, newFlags);
if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
newFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else if ((stateFlag & FLAG_DARK_NAV) != 0) {
newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
}
if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
newFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
} }
if (newFlags != oldFlags) { if (newFlags != oldFlags) {
mWindow.getDecorView().setSystemUiVisibility(newFlags); mWindow.getDecorView().setSystemUiVisibility(newFlags);
} }
} }
/**
* Returns the sysui visibility for the base layer
*/
public int getBaseSysuiVisibility() {
return getSysUiVisibilityFlags(
mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility());
}
private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
if (Utilities.ATLEAST_OREO) {
if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else if ((stateFlag & FLAG_DARK_NAV) != 0) {
currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
}
if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
return currentVisibility;
}
@Override @Override
public String toString() { public String toString() {
return "mStates=" + Arrays.toString(mStates); return "mStates=" + Arrays.toString(mStates);
@@ -125,11 +125,35 @@ public class ArrowTipView extends AbstractFloatingView {
* Show Tip with specified string and Y location * Show Tip with specified string and Y location
*/ */
public ArrowTipView show(String text, int top) { public ArrowTipView show(String text, int top) {
return show(text, Gravity.CENTER_HORIZONTAL, 0, top);
}
/**
* Show the ArrowTipView (tooltip) center, start, or end aligned.
*
* @param text The text to be shown in the tooltip.
* @param gravity The gravity aligns the tooltip center, start, or end.
* @param arrowMarginStart The margin from start to place arrow (ignored if center)
* @param top The Y coordinate of the bottom of tooltip.
* @return The tooltip.
*/
public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) {
((TextView) findViewById(R.id.text)).setText(text); ((TextView) findViewById(R.id.text)).setText(text);
mActivity.getDragLayer().addView(this); ViewGroup parent = mActivity.getDragLayer();
parent.addView(this);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL; params.gravity = gravity;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) findViewById(
R.id.arrow).getLayoutParams();
lp.gravity = gravity;
if (gravity == Gravity.END) {
lp.setMarginEnd(parent.getMeasuredWidth() - arrowMarginStart);
} else if (gravity == Gravity.START) {
lp.setMarginStart(arrowMarginStart);
}
requestLayout();
params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left; params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right; params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
post(() -> setY(top - getHeight())); post(() -> setY(top - getHeight()));
@@ -90,13 +90,23 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
} }
}; };
// Touch is being dispatched through the normal view dispatch system // Touch coming from normal view system is being dispatched.
private static final int TOUCH_DISPATCHING_VIEW = 1 << 0; private static final int TOUCH_DISPATCHING_FROM_VIEW = 1 << 0;
// Touch is being dispatched through the normal view dispatch system, and started at the // Touch is being dispatched through the normal view dispatch system, and started at the
// system gesture region // system gesture region. In this case we prevent internal gesture handling and only allow
private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1; // normal view event handling.
// Touch is being dispatched through a proxy from InputMonitor private static final int TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION = 1 << 1;
private static final int TOUCH_DISPATCHING_PROXY = 1 << 2; // Touch coming from InputMonitor proxy is being dispatched 'only to gestures'. Note that both
// this and view-system can be active at the same time where view-system would go to the views,
// and this would go to the gestures.
// Note that this is not set when events are coming from proxy, but going through full dispatch
// process (both views and gestures) to allow view-system to easily take over in case it
// comes later.
private static final int TOUCH_DISPATCHING_FROM_PROXY = 1 << 2;
// ACTION_DOWN has been dispatched to child views and ACTION_UP or ACTION_CANCEL is pending.
// Note that the event source can either be view-dispatching or proxy-dispatching based on if
// TOUCH_DISPATCHING_VIEW is present or not.
private static final int TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS = 1 << 3;
protected final float[] mTmpXY = new float[2]; protected final float[] mTmpXY = new float[2];
protected final float[] mTmpRectPoints = new float[4]; protected final float[] mTmpRectPoints = new float[4];
@@ -204,7 +214,8 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
protected boolean findActiveController(MotionEvent ev) { protected boolean findActiveController(MotionEvent ev) {
mActiveController = null; mActiveController = null;
if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) { if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
| TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
// Only look for controllers if we are not dispatching from gesture area and proxy is // Only look for controllers if we are not dispatching from gesture area and proxy is
// not active // not active
mActiveController = findControllerToHandleTouch(ev); mActiveController = findControllerToHandleTouch(ev);
@@ -283,19 +294,28 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
public boolean dispatchTouchEvent(MotionEvent ev) { public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) { switch (ev.getAction()) {
case ACTION_DOWN: { case ACTION_DOWN: {
mTouchDispatchState |= TOUCH_DISPATCHING_VIEW; if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
// Cancel the previous touch
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
super.dispatchTouchEvent(ev);
ev.setAction(action);
}
mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW
| TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
if (isEventInLauncher(ev)) { if (isEventInLauncher(ev)) {
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE; mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
} else { } else {
mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE; mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
} }
break; break;
} }
case ACTION_CANCEL: case ACTION_CANCEL:
case ACTION_UP: case ACTION_UP:
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE; mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW; mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW;
mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
break; break;
} }
super.dispatchTouchEvent(ev); super.dispatchTouchEvent(ev);
@@ -304,44 +324,54 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
return true; return true;
} }
/**
* Called before we are about to receive proxy events.
*
* @return false if we can't handle proxy at this time
*/
public boolean prepareProxyEventStarting() {
mProxyTouchController = null;
if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
// We are already dispatching using view system and have an active controller, we can't
// handle another controller.
// This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
// to be safe
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
return false;
}
mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
return true;
}
/** /**
* Proxies the touch events to the gesture handlers * Proxies the touch events to the gesture handlers
*/ */
public boolean proxyTouchEvent(MotionEvent ev) { public boolean proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch) {
boolean handled; int actionMasked = ev.getActionMasked();
if (mProxyTouchController != null) { boolean isViewDispatching = (mTouchDispatchState & TOUCH_DISPATCHING_FROM_VIEW) != 0;
handled = mProxyTouchController.onControllerTouchEvent(ev);
// Only do view dispatch if another view-dispatching is not running, or we already started
// proxy-dispatching before. Note that view-dispatching can always take over the proxy
// dispatching at anytime, but not vice-versa.
allowViewDispatch = allowViewDispatch && !isViewDispatching
&& (actionMasked == ACTION_DOWN
|| ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0));
if (allowViewDispatch) {
mTouchDispatchState |= TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
super.dispatchTouchEvent(ev);
if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
}
return true;
} else { } else {
mProxyTouchController = findControllerToHandleTouch(ev); boolean handled;
handled = mProxyTouchController != null; if (mProxyTouchController != null) {
handled = mProxyTouchController.onControllerTouchEvent(ev);
} else {
if (actionMasked == ACTION_DOWN) {
if (isViewDispatching && mActiveController != null) {
// A controller is already active, we can't initiate our own controller
mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
} else {
// We will control the handler via proxy
mTouchDispatchState |= TOUCH_DISPATCHING_FROM_PROXY;
}
}
if ((mTouchDispatchState & TOUCH_DISPATCHING_FROM_PROXY) != 0) {
mProxyTouchController = findControllerToHandleTouch(ev);
}
handled = mProxyTouchController != null;
}
if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
mProxyTouchController = null;
mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
}
return handled;
} }
int action = ev.getAction();
if (action == ACTION_UP || action == ACTION_CANCEL) {
mProxyTouchController = null;
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
}
return handled;
} }
/** /**
+16 -1
View File
@@ -27,6 +27,7 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -53,6 +54,7 @@ import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeL
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
@@ -77,7 +79,6 @@ import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.List; import java.util.List;
/** /**
* Simple scrim which draws a flat color * Simple scrim which draws a flat color
*/ */
@@ -115,6 +116,7 @@ public class ScrimView<T extends Launcher> extends View implements Insettable, O
private final WallpaperColorInfo mWallpaperColorInfo; private final WallpaperColorInfo mWallpaperColorInfo;
private final AccessibilityManager mAM; private final AccessibilityManager mAM;
protected final int mEndScrim; protected final int mEndScrim;
protected final boolean mIsScrimDark;
private final StateListener<LauncherState> mAccessibilityLauncherStateListener = private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
new StateListener<LauncherState>() { new StateListener<LauncherState>() {
@@ -156,6 +158,7 @@ public class ScrimView<T extends Launcher> extends View implements Insettable, O
mLauncher = Launcher.cast(Launcher.getLauncher(context)); mLauncher = Launcher.cast(Launcher.getLauncher(context));
mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context); mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor); mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
mMaxScrimAlpha = 0.7f; mMaxScrimAlpha = 0.7f;
@@ -233,6 +236,7 @@ public class ScrimView<T extends Launcher> extends View implements Insettable, O
mProgress = progress; mProgress = progress;
stopDragHandleEducationAnim(); stopDragHandleEducationAnim();
updateColors(); updateColors();
updateSysUiColors();
updateDragHandleAlpha(); updateDragHandleAlpha();
invalidate(); invalidate();
} }
@@ -245,6 +249,17 @@ public class ScrimView<T extends Launcher> extends View implements Insettable, O
mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha)); mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
} }
protected void updateSysUiColors() {
// Use a light system UI (dark icons) if all apps is behind at least half of the
// status bar.
boolean forceChange = mProgress <= 0.1f;
if (forceChange) {
mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
}
}
protected void updateDragHandleAlpha() { protected void updateDragHandleAlpha() {
if (mDragHandle != null) { if (mDragHandle != null) {
mDragHandle.setAlpha(mDragHandleAlpha); mDragHandle.setAlpha(mDragHandleAlpha);