Merge "TAPL: Verifying activity start/stop events" into ub-launcher3-master
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 1d0b045..04506b5 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,6 +73,17 @@
</intent-filter>
</provider>
+ <!-- FileProvider used for sharing images. -->
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="${packageName}.overview.fileprovider"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/overview_file_provider_paths" />
+ </provider>
+
<service
android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
tools:node="remove" />
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index da58817..5b01185 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -26,6 +26,7 @@
import androidx.core.app.NotificationCompat;
+import com.android.launcher3.ArrowTipView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
@@ -42,6 +43,7 @@
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.Snackbar;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -56,7 +58,11 @@
private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
private static final int ONBOARDING_NOTIFICATION_ID = 7641;
+ private static final String SETTINGS_ACTION =
+ "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
+
private final Launcher mLauncher;
+ private final Hotseat mHotseat;
private final NotificationManager mNotificationManager;
private final Notification mNotification;
private List<WorkspaceItemInfo> mPredictedApps;
@@ -68,6 +74,7 @@
HotseatEduController(Launcher launcher, Runnable runnable) {
mLauncher = launcher;
+ mHotseat = launcher.getHotseat();
mOnOnboardingComplete = runnable;
mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
createNotificationChannel();
@@ -98,7 +105,7 @@
//separate folders and items that can get in folders
for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
- View view = mLauncher.getHotseat().getChildAt(i, 0);
+ View view = mHotseat.getChildAt(i, 0);
if (view == null) continue;
ItemInfo info = (ItemInfo) view.getTag();
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
@@ -178,7 +185,6 @@
*/
private int migrateHotseatWhole() {
Workspace workspace = mLauncher.getWorkspace();
- Hotseat hotseatVG = mLauncher.getHotseat();
int pageId = -1;
int toRow = 0;
@@ -196,7 +202,7 @@
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
}
for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
- View child = hotseatVG.getChildAt(i, 0);
+ View child = mHotseat.getChildAt(i, 0);
if (child == null || child.getTag() == null) continue;
ItemInfo tag = (ItemInfo) child.getTag();
mLauncher.getModelWriter().moveItemInDatabase(tag,
@@ -211,8 +217,8 @@
mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
}
- void finishOnboarding() {
- mLauncher.getHotseat().removeAllViewsInLayout();
+ void moveHotseatItems() {
+ mHotseat.removeAllViewsInLayout();
if (!mNewItems.isEmpty()) {
int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
ArrayList<ItemInfo> animated = new ArrayList<>();
@@ -227,17 +233,34 @@
}
mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
}
+ }
+
+ void finishOnboarding() {
mOnOnboardingComplete.run();
destroy();
mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
}
+ void showDimissTip() {
+ if (mHotseat.getShortcutsAndWidgets().getChildCount()
+ < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off,
+ null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+ } else {
+ new ArrowTipView(mLauncher).show(
+ mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+ }
+ }
+
void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
mPredictedApps = predictedApps;
if (!mPredictedApps.isEmpty()
&& mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
}
+ else {
+ removeNotification();
+ }
}
private void createNotificationChannel() {
@@ -275,6 +298,17 @@
}
}
+ void showEdu() {
+ // hotseat is already empty and does not require migration. show edu tip
+ if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) {
+ new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled),
+ mHotseat.getTop());
+ finishOnboarding();
+ } else {
+ showDialog();
+ }
+ }
+
void showDialog() {
if (mPredictedApps == null || mPredictedApps.isEmpty()) {
return;
@@ -291,7 +325,7 @@
ActivityTracker.SchedulerCallback<QuickstepLauncher> {
@Override
public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
- activity.getHotseatPredictionController().showEduDialog();
+ activity.getHotseatPredictionController().showEdu();
return true;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index bcce168..8944088 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -16,7 +16,8 @@
package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType
+ .HYBRID_HOTSEAT_CANCELED;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -27,9 +28,7 @@
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
-import android.widget.Toast;
-import com.android.launcher3.ArrowTipView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@@ -109,18 +108,16 @@
private void onAccept(View v) {
mHotseatEduController.migrate();
handleClose(true);
+
+ mHotseatEduController.moveHotseatItems();
mHotseatEduController.finishOnboarding();
//TODO: pass actual page index here.
// Temporarily we're passing 1 for folder migration and 2 for page migration
logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
- int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()
- ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
- Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
}
private void onDismiss(View v) {
- int top = mLauncher.getHotseat().getTop();
- new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top);
+ mHotseatEduController.showDimissTip();
mHotseatEduController.finishOnboarding();
logUserAction(false, -1);
handleClose(true);
@@ -166,6 +163,7 @@
target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
// encoding migration type on pageIndex
target.pageIndex = pageIndex;
+ target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 801408f..d3bb4f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -16,6 +16,9 @@
package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.logging.LoggerUtils.newAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -28,6 +31,7 @@
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.os.Bundle;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -59,9 +63,11 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.DeviceFlag;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -85,6 +91,9 @@
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
+ public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt(
+ DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5);
+
//TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
private static final int APPTARGET_ACTION_UNPIN = 4;
@@ -113,8 +122,8 @@
private HotseatEduController mHotseatEduController;
- private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+ private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
private final View.OnLongClickListener mPredictionLongClickListener = v -> {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
@@ -147,12 +156,12 @@
}
/**
- * Transitions to NORMAL workspace mode and shows edu dialog
+ * Transitions to NORMAL workspace mode and shows edu
*/
- public void showEduDialog() {
+ public void showEdu() {
if (mHotseatEduController == null) return;
mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
- () -> mHotseatEduController.showDialog());
+ () -> mHotseatEduController.showEdu());
}
@Override
@@ -278,7 +287,7 @@
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
setPauseUIUpdate(false);
-
+ performBetaCheck();
if (!isReady()) {
mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
}
@@ -590,6 +599,39 @@
}
}
+ private void performBetaCheck() {
+ if (isReady()) return;
+ int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+
+ // -1 to exclude smart space
+ int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
+ Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
+
+ // opt user into the feature without onboarding tip or migration if they don't have any
+ // open spots in their hotseat and have more than maxItems in their hotseat + workspace
+
+ if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount
+ > MAX_ITEMS_FOR_MIGRATION) {
+ mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+ true).apply();
+
+ LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH);
+ LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP);
+ action.touch = LauncherLogProto.Action.Touch.TAP;
+ target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+ target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
+ // temporarily encode details in log target (go/hotseat_migration)
+ target.rank = 2;
+ target.cardinality = MAX_ITEMS_FOR_MIGRATION;
+ target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount;
+ LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+ UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null);
+
+
+ }
+ }
+
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d1a487a..a6eea0c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -183,8 +183,8 @@
onStateOrResumeChanged();
}
- if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
- && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+ if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+ || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
mHotseatPredictionController.setPauseUIUpdate(false);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 6fc03b1..8af2747 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -73,7 +73,11 @@
super(l, false /* allowDragToOverview */);
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
- mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT;
+ mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
+ }
+
+ protected float getMotionPauseMaxDisplacement() {
+ return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 064133c..71aa2e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -66,6 +66,13 @@
}
@Override
+ protected float getMotionPauseMaxDisplacement() {
+ // No need to disallow pause when swiping up all the way up the screen (unlike
+ // FlingAndHoldTouchController where user is probably intending to go to all apps).
+ return Float.MAX_VALUE;
+ }
+
+ @Override
protected boolean canInterceptTouch(MotionEvent ev) {
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
return super.canInterceptTouch(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
new file mode 100644
index 0000000..33fe5a9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -0,0 +1,94 @@
+/*
+ * 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.quickstep;
+
+import static android.content.Intent.EXTRA_STREAM;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.util.ImageActionUtils;
+
+import java.util.function.Supplier;
+
+/**
+ * Contains image selection functions necessary to complete overview action button functions.
+ */
+public class ImageActionsApi {
+
+ private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
+ private final Context mContext;
+ private final Supplier<Bitmap> mBitmapSupplier;
+ private final SystemUiProxy mSystemUiProxy;
+
+ public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
+ mContext = context;
+ mBitmapSupplier = bitmapSupplier;
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+ }
+
+ /**
+ * Share the image this api was constructed with using the provided intent. The implementation
+ * should add an {@link Intent#EXTRA_STREAM} with the URI pointing to the image to the intent.
+ */
+ @UiThread
+ public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+ if (mBitmapSupplier.get() == null) {
+ Log.e(TAG, "No snapshot available, not starting share.");
+ return;
+ }
+
+ UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
+ mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
+ intentForUri.putExtra(EXTRA_STREAM, uri);
+ return new Intent[]{intentForUri};
+ }, TAG));
+
+ }
+
+ /**
+ * Share the image this api was constructed with.
+ */
+ @UiThread
+ public void startShareActivity() {
+ ImageActionUtils.startShareActivity(mContext, mBitmapSupplier, null, null, TAG);
+ }
+
+ /**
+ * @param screenshot to be saved to the media store.
+ * @param screenshotBounds the location of where the bitmap was laid out on the screen in
+ * screen coordinates.
+ * @param visibleInsets that are used to draw the screenshot within the bounds.
+ * @param taskId of the task that the screenshot was taken of.
+ */
+ public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds,
+ Insets visibleInsets, int taskId) {
+ ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets,
+ taskId);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 33d9d9a..fbf29af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,12 +16,13 @@
package com.android.quickstep;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Matrix;
-import android.view.View;
-
-import androidx.annotation.Nullable;
+import android.graphics.Rect;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
@@ -29,6 +30,7 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.plugins.OverscrollPlugin;
@@ -43,16 +45,6 @@
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
- /** Note that these will be shown in order from top to bottom, if available for the task. */
- private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
- TaskShortcutFactory.APP_INFO,
- TaskShortcutFactory.SPLIT_SCREEN,
- TaskShortcutFactory.PIN,
- TaskShortcutFactory.INSTALL,
- TaskShortcutFactory.FREE_FORM,
- TaskShortcutFactory.WELLBEING
- };
-
public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
@@ -76,25 +68,68 @@
}
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
- return new TaskOverlay();
+ return new TaskOverlay(thumbnailView);
}
+ /** Note that these will be shown in order from top to bottom, if available for the task. */
+ private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
+ TaskShortcutFactory.APP_INFO,
+ TaskShortcutFactory.SPLIT_SCREEN,
+ TaskShortcutFactory.PIN,
+ TaskShortcutFactory.INSTALL,
+ TaskShortcutFactory.FREE_FORM,
+ TaskShortcutFactory.WELLBEING
+ };
+
+ /**
+ * Overlay on each task handling Overview Action Buttons.
+ */
public static class TaskOverlay {
+ private final Context mApplicationContext;
+ private OverviewActionsView mActionsView;
+ private final TaskThumbnailView mThumbnailView;
+
+
+ protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
+ mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
+ mThumbnailView = taskThumbnailView;
+ }
+
/**
* 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);
- @Nullable
- public View getActionsView() {
- return null;
+ if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
+ && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
+ mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+ R.id.overview_actions_view);
+ }
+ if (mActionsView != null) {
+ mActionsView.setListener(new OverviewActionsView.Listener() {
+ @Override
+ public void onShare() {
+ imageApi.startShareActivity();
+ }
+
+ @Override
+ public void onScreenshot() {
+ imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+ getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+ }
+ });
+ }
+
}
/**
* Called when the overlay is no longer used.
*/
- public void reset() { }
+ public void reset() {
+ }
/**
* Whether the overlay is modal, which means only tapping is enabled, but no swiping.
@@ -102,5 +137,28 @@
public boolean isOverlayModal() {
return false;
}
+
+ /**
+ * Gets the task snapshot as it is displayed on the screen.
+ *
+ * @return the bounds of the snapshot in screen coordinates.
+ */
+ public Rect getTaskSnapshotBounds() {
+ int[] location = new int[2];
+ mThumbnailView.getLocationOnScreen(location);
+
+ return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0],
+ mThumbnailView.getHeight() + location[1]);
+ }
+
+ /**
+ * Gets the insets that the snapshot is drawn with.
+ *
+ * @return the insets in screen coordinates.
+ */
+ public Insets getTaskSnapshotInsets() {
+ // TODO: return the real insets
+ return Insets.of(0, 0, 0, 0);
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
new file mode 100644
index 0000000..6a37e2b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -0,0 +1,79 @@
+/*
+ * 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.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/**
+ * View for showing action buttons in Overview
+ */
+public class OverviewActionsView extends FrameLayout {
+
+ private final View mScreenshotButton;
+ private final View mShareButton;
+
+ /**
+ * Listener for taps on the various actions.
+ */
+ public interface Listener {
+ /** User has initiated the share actions. */
+ void onShare();
+
+ /** User has initiated the screenshot action. */
+ void onScreenshot();
+ }
+
+ public OverviewActionsView(Context context) {
+ this(context, null);
+ }
+
+ public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
+ mShareButton = findViewById(R.id.action_share);
+ mScreenshotButton = findViewById(R.id.action_screenshot);
+ }
+
+ /**
+ * Set listener for callbacks on action button taps.
+ *
+ * @param listener for callbacks, or {@code null} to clear the listener.
+ */
+ public void setListener(@Nullable OverviewActionsView.Listener listener) {
+ mShareButton.setOnClickListener(
+ listener == null ? null : view -> listener.onShare());
+ mScreenshotButton.setOnClickListener(
+ listener == null ? null : view -> listener.onScreenshot());
+ }
+}
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..d97eae1
--- /dev/null
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -0,0 +1,23 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,21L7,21v-1h10v1zM17,18L7,18L7,6h10v12zM17,4L7,4L7,3h10v1zM9.5,8.5L12,8.5L12,7L8,7v4h1.5zM12,17h4v-4h-1.5v2.5L12,15.5z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_share.xml b/quickstep/res/drawable/ic_share.xml
new file mode 100644
index 0000000..ff4baec
--- /dev/null
+++ b/quickstep/res/drawable/ic_share.xml
@@ -0,0 +1,23 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,16c-0.79,0 -1.5,0.31 -2.03,0.81L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.53,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.48 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.05,4.12c-0.05,0.22 -0.09,0.45 -0.09,0.69 0,1.66 1.34,3 3,3s3,-1.34 3,-3 -1.34,-3 -3,-3zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
new file mode 100644
index 0000000..ad5efb6
--- /dev/null
+++ b/quickstep/res/layout/overview_actions.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <LinearLayout
+ android:id="@+id/action_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+ <Button
+ android:id="@+id/action_screenshot"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_screenshot"
+ android:text="@string/action_screenshot" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+
+ <Button
+ android:id="@+id/action_share"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_share"
+ android:text="@string/action_share" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+ </LinearLayout>
+
+</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
new file mode 100644
index 0000000..328c20b
--- /dev/null
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.quickstep.views.OverviewActionsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+
+</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 988c78d..8d42c4a 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -23,7 +23,7 @@
<dimen name="task_corner_radius_small">2dp</dimen>
<!-- Overrideable in overlay that provides the Overview Actions. -->
- <dimen name="overview_actions_height">0dp</dimen>
+ <dimen name="overview_actions_height">110dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index b55b042..40d7c7a 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -78,16 +78,21 @@
<string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
<string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
- <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
- <string name="hotseat_items_migrated">Your hotseat items have been moved up to your homescreen</string>
- <string name="hotseat_items_migrated_alt">Your hotseat items have been moved to a folder</string>
- <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
- <string name="hotseat_no_migration">Drag apps off the bottom row to see app suggestions</string>
<!-- Button text to opt in for fully predicted hotseat -->
<string name="hotseat_edu_accept">Get app suggestions</string>
<!-- Button text to dismiss opt in for fully predicted hotseat -->
<string name="hotseat_edu_dismiss">No thanks</string>
+ <!-- action shown to turn off predictions after onboarding -->
+ <string name="hotseat_turn_off">Settings</string>
+
+ <!-- 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>
+ <!-- tip shown if user declines migration and has no open spots for prediction -->
+ <string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
+ <!-- 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>
+
<!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
<string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
@@ -108,4 +113,10 @@
<string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
<!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
<string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
+
+ <!-- ******* Overview ******* -->
+ <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
+ <string translatable="false" name="action_share">Share</string>
+ <!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
+ <string translatable="false" name="action_screenshot">Screenshot</string>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index c8d7777..bf107fb 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -60,4 +60,13 @@
parent="TextAppearance.BackGestureTutorial.ButtonLabel">
<item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
</style>
+
+ <style name="OverviewActionButton"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+ <item name="android:textColor">?attr/workspaceTextColor</item>
+ <item name="android:drawableTint">?attr/workspaceTextColor</item>
+ <item name="android:tint">?attr/workspaceTextColor</item>
+ <item name="android:drawablePadding">4dp</item>
+ <item name="android:textAllCaps">false</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/xml/overview_file_provider_paths.xml b/quickstep/res/xml/overview_file_provider_paths.xml
new file mode 100644
index 0000000..14d7459
--- /dev/null
+++ b/quickstep/res/xml/overview_file_provider_paths.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <cache-path name="shared_images" path="/" />
+ <files-path name="log_files" path="/" />
+</paths>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index 5836ebd..010694b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -54,6 +54,9 @@
@Override
public void addChangeListener(Context context, Runnable r) {
+ if (mListeners == null) {
+ initialize(context);
+ }
mListeners.add(r);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1299a53..491c611 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -22,6 +22,7 @@
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -356,6 +357,7 @@
return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
new file mode 100644
index 0000000..7760255
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -0,0 +1,164 @@
+/*
+ * 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.quickstep.util;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.FileProvider;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.SystemUiProxy;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * Utility class containing methods to help manage image actions such as sharing, cropping, and
+ * saving image.
+ */
+public class ImageActionUtils {
+
+ private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+
+ /**
+ * Saves screenshot to location determine by SystemUiProxy
+ */
+ public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
+ Rect screenshotBounds,
+ Insets visibleInsets, int taskId) {
+ systemUiProxy.handleImageAsScreenshot(screenshot, screenshotBounds, visibleInsets, taskId);
+ }
+
+ /**
+ * Launch the activity to share image.
+ */
+ @UiThread
+ public static void startShareActivity(Context context, Supplier<Bitmap> bitmapSupplier,
+ Rect crop, Intent intent, String tag) {
+ if (bitmapSupplier.get() == null) {
+ Log.e(tag, "No snapshot available, not starting share.");
+ return;
+ }
+
+ UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context,
+ bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri,
+ tag));
+ }
+
+ /**
+ * Starts activity based on given intent created from image uri.
+ */
+ @WorkerThread
+ public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
+ Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
+ context.startActivities(
+ uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+ }
+
+ /**
+ * Converts image bitmap to Uri by temporarily saving bitmap to cache, and creating Uri pointing
+ * to that location. Used to be able to share an image with another app.
+ *
+ * @param bitmap The whole bitmap to be shared.
+ * @param crop The section of the bitmap to be shared.
+ * @param context The application context, used to interact with file system.
+ * @param tag Tag used to log errors.
+ * @return Uri that points to the cropped version of desired bitmap to share.
+ */
+ @WorkerThread
+ public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+ Bitmap croppedBitmap = cropBitmap(bitmap, crop);
+ int cropHash = crop == null ? 0 : crop.hashCode();
+ String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
+ File file = new File(context.getCacheDir(), baseName);
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
+ } catch (IOException e) {
+ Log.e(tag, "Error saving image", e);
+ }
+
+ return FileProvider.getUriForFile(context, AUTHORITY, file);
+ }
+
+ /**
+ * Crops the bitmap to the provided size and returns a software backed bitmap whenever possible.
+ *
+ * @param bitmap The bitmap to be cropped.
+ * @param crop The section of the bitmap in the crop.
+ * @return The cropped bitmap.
+ */
+ @WorkerThread
+ public static Bitmap cropBitmap(Bitmap bitmap, Rect crop) {
+ Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ if (crop == null) {
+ crop = new Rect(src);
+ }
+ if (crop.equals(src)) {
+ return bitmap;
+ } else {
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+ return Bitmap.createBitmap(bitmap, crop.left, crop.top, crop.width(),
+ crop.height());
+ }
+
+ // For hardware bitmaps, use the Picture API to directly create a software bitmap
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(crop.width(), crop.height());
+ canvas.drawBitmap(bitmap, -crop.left, -crop.top, null);
+ picture.endRecording();
+ return Bitmap.createBitmap(picture, crop.width(), crop.height(),
+ Bitmap.Config.ARGB_8888);
+ }
+ }
+
+ /**
+ * Gets the intent used to share image.
+ */
+ @WorkerThread
+ private static Intent[] getShareIntentForImageUri(Uri uri, Intent intent) {
+ if (intent == null) {
+ intent = new Intent();
+ }
+ intent.setAction(Intent.ACTION_SEND)
+ .setComponent(null)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setType("image/png")
+ .setData(uri)
+ .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
+ .putExtra(Intent.EXTRA_STREAM, uri);
+ return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b9e1aeb..c00a679 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -114,6 +114,7 @@
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
@@ -137,6 +138,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@@ -325,6 +327,9 @@
private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
private long mLastTouchUpTime = -1;
+ private boolean mTouchInProgress;
+
+ private SafeCloseable mUserChangedCallbackCloseable;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -444,6 +449,9 @@
});
TraceHelper.INSTANCE.endSection(traceToken);
+
+ mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
+ () -> getStateManager().goToState(NORMAL));
}
protected LauncherOverlayManager getDefaultOverlay() {
@@ -745,8 +753,8 @@
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
- mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(
appWidgetId, requestArgs, null,
@@ -776,15 +784,9 @@
"returned from the widget configuration activity.");
result = RESULT_CANCELED;
completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
- final Runnable onComplete = new Runnable() {
- @Override
- public void run() {
- getStateManager().goToState(NORMAL);
- }
- };
-
- mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
+ () -> getStateManager().goToState(NORMAL));
} else {
if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank)
@@ -803,8 +805,8 @@
dropLayout.setDropPending(false);
}
};
- mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
}
return;
}
@@ -823,12 +825,12 @@
// Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
completeAdd(requestCode, data, -1, requestArgs);
- mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_CANCELED) {
- mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
- ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
}
}
@@ -1578,6 +1580,7 @@
mOverlayManager.onActivityDestroyed(this);
mAppTransitionManager.unregisterRemoteAnimations();
+ mUserChangedCallbackCloseable.close();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1671,7 +1674,7 @@
};
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this));
- mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
+ mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
}
}
@@ -1829,13 +1832,28 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_UP) {
- mLastTouchUpTime = System.currentTimeMillis();
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mTouchInProgress = true;
+ break;
+ case MotionEvent.ACTION_UP:
+ mLastTouchUpTime = System.currentTimeMillis();
+ // Follow through
+ case MotionEvent.ACTION_CANCEL:
+ mTouchInProgress = false;
+ break;
}
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
return super.dispatchTouchEvent(ev);
}
+ /**
+ * Returns true if a touch interaction is in progress
+ */
+ public boolean isTouchInProgress() {
+ return mTouchInProgress;
+ }
+
@Override
public void onBackPressed() {
if (finishAutoCancelActionMode()) {
@@ -2101,7 +2119,7 @@
}
// Remove the extra empty screen
- mWorkspace.removeExtraEmptyScreen(false, false);
+ mWorkspace.removeExtraEmptyScreen(false);
}
/**
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a699c32..8d20bd6 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
+import java.util.function.Supplier;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
@@ -145,7 +146,7 @@
*/
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
- mOpenHelper = new DatabaseHelper(getContext());
+ mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
if (RestoreDbTask.isPending(getContext())) {
if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -159,17 +160,17 @@
}
}
- private synchronized boolean updateCurrentOpenHelper() {
- final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
- if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) {
+ private synchronized boolean prepForMigration(String dbFile, String targetTableName,
+ Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
+ if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
return false;
}
- DatabaseHelper oldHelper = mOpenHelper;
- mOpenHelper = new DatabaseHelper(getContext());
- copyTable(oldHelper.getReadableDatabase(), Favorites.TABLE_NAME,
- mOpenHelper.getWritableDatabase(), Favorites.TMP_TABLE, getContext());
- oldHelper.close();
+ final DatabaseHelper helper = src.get();
+ mOpenHelper = dst.get();
+ copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
+ mOpenHelper.getWritableDatabase(), targetTableName, getContext());
+ helper.close();
return true;
}
@@ -425,7 +426,23 @@
if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
Bundle result = new Bundle();
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
- updateCurrentOpenHelper());
+ prepForMigration(
+ InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
+ Favorites.TMP_TABLE,
+ () -> mOpenHelper,
+ () -> DatabaseHelper.createDatabaseHelper(getContext())));
+ return result;
+ }
+ }
+ case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
+ if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+ Bundle result = new Bundle();
+ result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+ prepForMigration(
+ arg /* dbFile */,
+ Favorites.PREVIEW_TABLE_NAME,
+ () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+ () -> mOpenHelper));
return result;
}
}
@@ -596,23 +613,31 @@
private int mMaxScreenId = -1;
private boolean mBackupTableExists;
- DatabaseHelper(Context context) {
- this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
- context).dbFile : LauncherFiles.LAUNCHER_DB);
+ static DatabaseHelper createDatabaseHelper(Context context) {
+ return createDatabaseHelper(context, null);
+ }
+
+ static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+ if (dbName == null) {
+ dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
+ context).dbFile : LauncherFiles.LAUNCHER_DB;
+ }
+ DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
- if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) {
+ if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
// This operation is a no-op if the table already exists.
- addFavoritesTable(getWritableDatabase(), true);
+ databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
}
if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
- mBackupTableExists = tableExists(getReadableDatabase(),
- Favorites.BACKUP_TABLE_NAME);
+ databaseHelper.mBackupTableExists = tableExists(
+ databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
}
- initIds();
+ databaseHelper.initIds();
+ return databaseHelper;
}
/**
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f516446..5262b18 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -326,10 +326,16 @@
public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
+ public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
+
public static final String EXTRA_VALUE = "value";
public static Bundle call(ContentResolver cr, String method) {
- return cr.call(CONTENT_URI, method, null, null);
+ return call(cr, method, null);
+ }
+
+ public static Bundle call(ContentResolver cr, String method, String arg) {
+ return cr.call(CONTENT_URI, method, arg, null);
}
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ee08beb..ee9c099 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -29,7 +29,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
@@ -124,9 +123,6 @@
private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
- private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
- private static final int FADE_EMPTY_SCREEN_DURATION = 150;
-
private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
private static final int DEFAULT_PAGE = 0;
@@ -139,7 +135,6 @@
@Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
@Thunk final IntArray mScreenOrder = new IntArray();
- @Thunk Runnable mRemoveEmptyScreenRunnable;
@Thunk boolean mDeferRemoveExtraEmptyScreen = false;
/**
@@ -427,7 +422,7 @@
}
if (!mDeferRemoveExtraEmptyScreen) {
- removeExtraEmptyScreen(true, mDragSourceInternal != null);
+ removeExtraEmptyScreen(mDragSourceInternal != null);
}
updateChildrenLayersEnabled();
@@ -452,8 +447,16 @@
private void setupLayoutTransition() {
// We want to show layout transitions when pages are deleted, to close the gap.
mLayoutTransition = new LayoutTransition();
+
mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ // Change the interpolators such that the fade animation plays before the move animation.
+ // This prevents empty adjacent pages to overlay during animation
+ mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
+ Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f));
+ mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
+ Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1));
+
mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
setLayoutTransition(mLayoutTransition);
@@ -570,9 +573,6 @@
boolean lastChildOnScreen = false;
boolean childOnFinalScreen = false;
- // Cancel any pending removal of empty screen
- mRemoveEmptyScreenRunnable = null;
-
if (mDragSourceInternal != null) {
if (mDragSourceInternal.getChildCount() == 1) {
lastChildOnScreen = true;
@@ -623,43 +623,34 @@
}
}
- public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
- removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
+ public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
+ removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
}
- public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
- final int delay, final boolean stripEmptyScreens) {
+ public void removeExtraEmptyScreenDelayed(
+ int delay, boolean stripEmptyScreens, Runnable onComplete) {
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading
return;
}
if (delay > 0) {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
- }
- }, delay);
+ postDelayed(
+ () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
return;
}
convertFinalScreenToEmptyScreenIfNecessary();
if (hasExtraEmptyScreen()) {
- int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
- if (getNextPage() == emptyIndex) {
- snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
- fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
- onComplete, stripEmptyScreens);
- } else {
- snapToPage(getNextPage(), 0);
- fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
- onComplete, stripEmptyScreens);
- }
- return;
- } else if (stripEmptyScreens) {
- // If we're not going to strip the empty screens after removing
- // the extra empty screen, do it right away.
+ removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID));
+ mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+ mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
+
+ // Update the page indicator to reflect the removed page.
+ showPageIndicatorAtCurrentScroll();
+ }
+
+ if (stripEmptyScreens) {
stripEmptyScreens();
}
@@ -668,44 +659,6 @@
}
}
- private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
- final boolean stripEmptyScreens) {
- // XXX: Do we need to update LM workspace screens below?
- final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
-
- mRemoveEmptyScreenRunnable = new Runnable() {
- @Override
- public void run() {
- if (hasExtraEmptyScreen()) {
- mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
- mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
- removeView(cl);
- if (stripEmptyScreens) {
- stripEmptyScreens();
- }
- // Update the page indicator to reflect the removed page.
- showPageIndicatorAtCurrentScroll();
- }
- }
- };
-
- ObjectAnimator oa = ObjectAnimator.ofFloat(cl, ALPHA, 0f);
- oa.setDuration(duration);
- oa.setStartDelay(delay);
- oa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mRemoveEmptyScreenRunnable != null) {
- mRemoveEmptyScreenRunnable.run();
- }
- if (onComplete != null) {
- onComplete.run();
- }
- }
- });
- oa.start();
- }
-
public boolean hasExtraEmptyScreen() {
return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
}
@@ -792,8 +745,6 @@
}
}
- boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
-
// We enforce at least one page to add new items to. In the case that we remove the last
// such screen, we convert the last screen to the empty screen
int minScreens = 1;
@@ -812,7 +763,6 @@
removeView(cl);
} else {
// if this is the last screen, convert it to the empty screen
- mRemoveEmptyScreenRunnable = null;
mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index dd0212a..8d1a102 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -406,6 +406,11 @@
setupWorkToggle();
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
+ mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
+ findViewById(R.id.tab_personal)
+ .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
+ findViewById(R.id.tab_work)
+ .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
onTabChanged(mViewPager.getNextPage());
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
@@ -456,16 +461,10 @@
public void onTabChanged(int pos) {
mHeader.setMainActive(pos == 0);
- reset(true /* animate */);
- mViewPager.getPageIndicator().updateTabTextColor(pos);
if (mAH[pos].recyclerView != null) {
mAH[pos].recyclerView.bindFastScrollbar();
-
- findViewById(R.id.tab_personal)
- .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
- findViewById(R.id.tab_work)
- .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
}
+ reset(true /* animate */);
if (mWorkModeSwitch != null) {
mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 3e40392..2515c24 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -135,6 +135,7 @@
@Override
public void setActiveMarker(int activePage) {
updateTabTextColor(activePage);
+ updateIndicatorPosition(activePage);
if (mContainerView != null && mLastActivePage != activePage) {
mContainerView.onTabChanged(activePage);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index b3f87f0..92f5112 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -154,7 +154,7 @@
"Replace Smartspace with a version rendered by System UI.");
public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
- "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+ "ENABLE_LSQ_VELOCITY_PROVIDER", true,
"Use Least Square algorithm for motion pause detection.");
public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
@@ -163,7 +163,7 @@
"Always use hardware optimization for folder animations.");
public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
- FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false,
+ FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
"Launch/close apps without rotation animation. Fix Launcher to portrait");
public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index d6b9b76..db61f59 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.ATLEAST_Q;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.ValueAnimator;
import android.content.ComponentName;
@@ -62,7 +63,7 @@
private final FlingToDeleteHelper mFlingToDeleteHelper;
// temporaries to avoid gc thrash
- private Rect mRectTemp = new Rect();
+ private final Rect mRectTemp = new Rect();
private final int[] mCoordinatesTemp = new int[2];
/**
@@ -207,6 +208,11 @@
handleMoveEvent(mLastTouch.x, mLastTouch.y);
mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+
+ if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+ // If it is an internal drag and the touch is already complete, cancel immediately
+ MAIN_EXECUTOR.submit(this::cancelDrag);
+ }
return dragView;
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 5bc6610..7d4eb0e 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
-import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -397,7 +396,10 @@
private void populate() {
if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
- boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+ boolean needsToMigrate =
+ MULTI_DB_GRID_MIRATION_ALGO.get()
+ ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
+ : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
boolean success = false;
if (needsToMigrate) {
success = MULTI_DB_GRID_MIRATION_ALGO.get()
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 0bdccfa..79ae4c5 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -123,8 +123,16 @@
}
/**
- * Run the migration algorithm if needed. For preview, we provide the intended idp because it
- * has not been changed. If idp is null, we read it from the context, for actual grid migration.
+ * When migrating the grid for preview, we copy the table
+ * {@link LauncherSettings.Favorites.TABLE_NAME} into
+ * {@link LauncherSettings.Favorites.PREVIEW_TABLE_NAME}, run grid size migration from the
+ * former to the later, then use the later table for preview.
+ *
+ * Similarly when doing the actual grid migration, the former grid option's table
+ * {@link LauncherSettings.Favorites.TABLE_NAME} is copied into the new grid option's
+ * {@link LauncherSettings.Favorites.TMP_TABLE}, we then run the grid size migration algorithm
+ * to migrate the later to the former, and load the workspace from the default
+ * {@link LauncherSettings.Favorites.TABLE_NAME}.
*
* @return false if the migration failed.
*/
@@ -151,7 +159,14 @@
HashSet<String> validPackages = getValidPackages(context);
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
- if (!LauncherSettings.Settings.call(
+ if (migrateForPreview) {
+ if (!LauncherSettings.Settings.call(
+ context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW, idp.dbFile).getBoolean(
+ LauncherSettings.Settings.EXTRA_VALUE)) {
+ return false;
+ }
+ } else if (!LauncherSettings.Settings.call(
context.getContentResolver(),
LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER).getBoolean(
LauncherSettings.Settings.EXTRA_VALUE)) {
@@ -164,9 +179,13 @@
LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
LauncherSettings.Settings.EXTRA_VALUE)) {
- DbReader srcReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TMP_TABLE,
+ DbReader srcReader = new DbReader(t.getDb(),
+ migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
+ : LauncherSettings.Favorites.TMP_TABLE,
context, validPackages, srcHotseatCount);
- DbReader destReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TABLE_NAME,
+ DbReader destReader = new DbReader(t.getDb(),
+ migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
+ : LauncherSettings.Favorites.TABLE_NAME,
context, validPackages, idp.numHotseatIcons);
Point targetSize = new Point(idp.numColumns, idp.numRows);
@@ -174,7 +193,9 @@
srcReader, destReader, idp.numHotseatIcons, targetSize);
task.migrate();
- dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+ if (!migrateForPreview) {
+ dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+ }
t.commit();
return true;
@@ -186,11 +207,13 @@
Log.v(TAG, "Workspace migration completed in "
+ (System.currentTimeMillis() - migrationStartTime));
- // Save current configuration, so that the migration does not run again.
- prefs.edit()
- .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
- .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
- .apply();
+ if (!migrateForPreview) {
+ // Save current configuration, so that the migration does not run again.
+ prefs.edit()
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+ .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+ .apply();
+ }
}
}
@@ -202,7 +225,7 @@
// Migrate hotseat
HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
- mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
+ mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
hotseatSolution.find();
// Sort the items by the reading order.
@@ -215,7 +238,7 @@
}
List<DbEntry> entries = mDestReader.loadWorkspaceEntries(screenId);
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+ mDestReader, mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
workspaceSolution.find();
if (mWorkspaceDiff.isEmpty()) {
break;
@@ -225,7 +248,8 @@
int screenId = mDestReader.mLastScreenId + 1;
while (!mWorkspaceDiff.isEmpty()) {
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, mWorkspaceDiff);
+ mDestReader, mContext, new ArrayList<>(), screenId, mTrgX, mTrgY,
+ mWorkspaceDiff);
workspaceSolution.find();
screenId++;
}
@@ -246,7 +270,8 @@
}
private static void insertEntryInDb(SQLiteDatabase db, Context context,
- ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry) {
+ ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry, String srcTableName,
+ String destTableName) {
int id = -1;
switch (entry.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
@@ -283,8 +308,8 @@
return;
}
- Cursor c = db.query(LauncherSettings.Favorites.TMP_TABLE, null,
- LauncherSettings.Favorites._ID + " = '" + id + "'", null, null, null, null);
+ Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'",
+ null, null, null, null);
while (c.moveToNext()) {
ContentValues values = new ContentValues();
@@ -294,14 +319,14 @@
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
LauncherSettings.Settings.EXTRA_VALUE));
- db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
+ db.insert(destTableName, null, values);
}
c.close();
}
- private static void removeEntryFromDb(SQLiteDatabase db, IntArray entryId) {
- db.delete(LauncherSettings.Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, entryId), null);
+ private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) {
+ db.delete(tableName,
+ Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null);
}
private static HashSet<String> getValidPackages(Context context) {
@@ -325,6 +350,7 @@
private final SQLiteDatabase mDb;
private final DbReader mSrcReader;
+ private final DbReader mDestReader;
private final Context mContext;
private final GridOccupancy mOccupied;
private final int mScreenId;
@@ -335,11 +361,12 @@
private int mNextStartX;
private int mNextStartY;
- GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
- List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
+ GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+ Context context, List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
int trgY, List<DbEntry> itemsToPlace) {
mDb = db;
mSrcReader = srcReader;
+ mDestReader = destReader;
mContext = context;
mOccupied = new GridOccupancy(trgX, trgY);
mScreenId = screenId;
@@ -362,7 +389,8 @@
continue;
}
if (findPlacement(entry)) {
- insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry);
+ insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry,
+ mSrcReader.mTableName, mDestReader.mTableName);
iterator.remove();
}
}
@@ -397,14 +425,17 @@
private final SQLiteDatabase mDb;
private final DbReader mSrcReader;
+ private final DbReader mDestReader;
private final Context mContext;
private final HotseatOccupancy mOccupied;
private final List<DbEntry> mItemsToPlace;
- HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
- int hotseatSize, List<DbEntry> placedHotseatItems, List<DbEntry> itemsToPlace) {
+ HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+ Context context, int hotseatSize, List<DbEntry> placedHotseatItems,
+ List<DbEntry> itemsToPlace) {
mDb = db;
mSrcReader = srcReader;
+ mDestReader = destReader;
mContext = context;
mOccupied = new HotseatOccupancy(hotseatSize);
for (DbEntry entry : placedHotseatItems) {
@@ -422,7 +453,8 @@
// to something other than -1.
entry.cellX = i;
entry.cellY = 0;
- insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry);
+ insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry,
+ mSrcReader.mTableName, mDestReader.mTableName);
mOccupied.markCells(entry, true);
}
}
@@ -519,7 +551,7 @@
}
mHotseatEntries.add(entry);
}
- removeEntryFromDb(mDb, entriesToRemove);
+ removeEntryFromDb(mDb, mTableName, entriesToRemove);
c.close();
return mHotseatEntries;
}
@@ -639,7 +671,7 @@
}
mWorkspaceEntries.add(entry);
}
- removeEntryFromDb(mDb, entriesToRemove);
+ removeEntryFromDb(mDb, mTableName, entriesToRemove);
c.close();
return mWorkspaceEntries;
}
@@ -657,7 +689,7 @@
total++;
entry.mFolderItems.add(intent);
} catch (Exception e) {
- removeEntryFromDb(mDb, IntArray.wrap(c.getInt(0)));
+ removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
}
}
c.close();
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 678b647..f723256 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -95,7 +95,7 @@
private void removeUserChangeListener(Runnable command) {
synchronized (this) {
- mUserChangeListeners.add(command);
+ mUserChangeListeners.remove(command);
if (mUserChangeListeners.isEmpty()) {
// Disable cache and stop listening
mContext.unregisterReceiver(mUserChangeReceiver);
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index dacea84..7e05a5a 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -129,6 +129,7 @@
toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db");
toDb.execSQL(
"INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable);
+ toDb.execSQL("DETACH DATABASE 'from_db'");
} else {
toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable);
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8c8aa9b..fae0fe2 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -134,7 +134,7 @@
*/
private void updateForcedRotation(boolean setValueFromPrefs) {
boolean isForcedRotation = mFeatureFlagsPrefs
- .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false)
+ .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
&& !getAllowRotationDefaultValue();
if (mForcedRotation == isForcedRotation) {
return;
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 793af48..a3c70ec 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -45,6 +45,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
@@ -267,8 +268,10 @@
}
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
+ final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT);
+ if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
assertTrue("Widget is not present",
- mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
+ widget != null);
}
private void verifyPendingWidgetPresent() {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 421edb0..d171a69 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -318,7 +318,7 @@
};
}
- private void dumpViewHierarchy() {
+ public void dumpViewHierarchy() {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
mDevice.dumpWindowHierarchy(stream);
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 084138c..a14d2f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -88,47 +88,50 @@
}
public Widget getWidget(String labelText) {
- final UiObject2 widgetsContainer = verifyActiveContainer();
- final Point displaySize = mLauncher.getRealDisplaySize();
- final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "getting widget " + labelText + " in widgets list")) {
+ final UiObject2 widgetsContainer = verifyActiveContainer();
+ final Point displaySize = mLauncher.getRealDisplaySize();
+ final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
- int i = 0;
- for (; ; ) {
- final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
- widgetsContainer, "widgets_scroll_container");
- mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
- for (UiObject2 cell : cells) {
- final UiObject2 label = cell.findObject(labelSelector);
- if (label == null) continue;
+ int i = 0;
+ for (; ; ) {
+ final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+ widgetsContainer, "widgets_scroll_container");
+ mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+ for (UiObject2 cell : cells) {
+ final UiObject2 label = cell.findObject(labelSelector);
+ if (label == null) continue;
- final UiObject2 widget = label.getParent().getParent();
- mLauncher.assertEquals(
- "View is not WidgetCell",
- "com.android.launcher3.widget.WidgetCell",
- widget.getClassName());
+ final UiObject2 widget = label.getParent().getParent();
+ mLauncher.assertEquals(
+ "View is not WidgetCell",
+ "com.android.launcher3.widget.WidgetCell",
+ widget.getClassName());
- int maxWidth = 0;
- for (UiObject2 sibling : widget.getParent().getChildren()) {
- maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+ int maxWidth = 0;
+ for (UiObject2 sibling : widget.getParent().getChildren()) {
+ maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+ }
+
+ int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+ if (visibleDelta > 0) {
+ Rect parentBounds = cell.getVisibleBounds();
+ mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
+ + mLauncher.getTouchSlop(),
+ parentBounds.centerY(), parentBounds.centerX(),
+ parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ }
+
+ if (widget.getVisibleBounds().bottom
+ <= displaySize.y - mLauncher.getBottomGestureSize()) {
+ return new Widget(mLauncher, widget);
+ }
}
- int visibleDelta = maxWidth - widget.getVisibleBounds().width();
- if (visibleDelta > 0) {
- Rect parentBounds = cell.getVisibleBounds();
- mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
- + mLauncher.getTouchSlop(),
- parentBounds.centerY(), parentBounds.centerX(),
- parentBounds.centerY(), 10, true, GestureScope.INSIDE);
- }
-
- if (widget.getVisibleBounds().bottom
- <= displaySize.y - mLauncher.getBottomGestureSize()) {
- return new Widget(mLauncher, widget);
- }
+ mLauncher.assertTrue("Too many attempts", ++i <= 40);
+ mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
}
-
- mLauncher.assertTrue("Too many attempts", ++i <= 40);
- mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 36c9849..9ef6476 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -286,16 +286,22 @@
@Nullable
public Widget tryGetWidget(String label, long timeout) {
- final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
- By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
- timeout);
- return widget != null ? new Widget(mLauncher, widget) : null;
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "getting widget " + label + " on workspace with timeout " + timeout)) {
+ final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+ By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+ timeout);
+ return widget != null ? new Widget(mLauncher, widget) : null;
+ }
}
@Nullable
public Widget tryGetPendingWidget(long timeout) {
- final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
- By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
- return widget != null ? new Widget(mLauncher, widget) : null;
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "getting pending widget on workspace with timeout " + timeout)) {
+ final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+ By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+ return widget != null ? new Widget(mLauncher, widget) : null;
+ }
}
}
\ No newline at end of file