Merge "Use PersistedTaskSnapshotData to calculate TaskView size" into sc-dev
diff --git a/Android.bp b/Android.bp
index a720658..9d675a4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -24,7 +24,6 @@
],
srcs: [
"tests/tapl/**/*.java",
- "src/com/android/launcher3/util/SecureSettingsObserver.java",
"src/com/android/launcher3/ResourceUtils.java",
"src/com/android/launcher3/testing/TestProtocol.java",
],
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 68c3851..a0e87cf 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -130,4 +130,5 @@
<dimen name="taskbar_icon_spacing">14dp</dimen>
<dimen name="taskbar_divider_thickness">1dp</dimen>
<dimen name="taskbar_divider_height">24dp</dimen>
+ <dimen name="taskbar_folder_margin">16dp</dimen>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5a353f0..df089f6 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -89,6 +89,5 @@
<!-- Icon displayed on the taskbar -->
<style name="BaseIcon.Workspace.Taskbar" >
<item name="iconDisplay">taskbar</item>
- <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
</style>
</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index edcd0a2..d1fa2fd 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -30,7 +30,6 @@
import android.content.IntentSender;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.Nullable;
@@ -43,7 +42,7 @@
import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.taskbar.TaskbarContainerView;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarController;
import com.android.launcher3.taskbar.TaskbarStateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
@@ -207,6 +206,7 @@
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
addTaskbarIfNecessary();
+ addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
}
@Override
@@ -223,9 +223,9 @@
mTaskbarController = null;
}
if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
- TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this)
- .inflate(R.layout.taskbar, null, false);
- mTaskbarController = new TaskbarController(this, taskbarContainer);
+ TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
+ mTaskbarController = new TaskbarController(this,
+ taskbarActivityContext.getTaskbarContainerView());
mTaskbarController.init();
}
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
new file mode 100644
index 0000000..96559cb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+
+import java.util.List;
+
+public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate {
+
+ public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) {
+ super(launcher);
+ mActions.put(PIN_PREDICTION, new LauncherAction(
+ PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P));
+ }
+
+ @Override
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
+ if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) {
+ out.add(new LauncherAction(PIN_PREDICTION, R.string.pin_prediction,
+ KeyEvent.KEYCODE_P));
+ }
+ super.getSupportedActions(host, item, out);
+ }
+
+ @Override
+ protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
+ QuickstepLauncher launcher = (QuickstepLauncher) mLauncher;
+ if (action == PIN_PREDICTION) {
+ if (launcher.getHotseatPredictionController() == null) {
+ return false;
+ }
+ launcher.getHotseatPredictionController().pinPrediction(item);
+ return true;
+ }
+ return super.performAction(host, item, action, fromKeyboard);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
new file mode 100644
index 0000000..06372fe
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.ContextWrapper;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
+ * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
+ * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
+ */
+public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
+
+ private final DeviceProfile mDeviceProfile;
+ private final LayoutInflater mLayoutInflater;
+ private final TaskbarContainerView mTaskbarContainerView;
+
+ public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
+ super(launcher);
+ mDeviceProfile = launcher.getDeviceProfile().copy(this);
+ float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+ float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+ mDeviceProfile.updateIconSize(iconScale, getResources());
+
+ mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+
+ mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater
+ .inflate(R.layout.taskbar, null, false);
+ }
+
+ public TaskbarContainerView getTaskbarContainerView() {
+ return mTaskbarContainerView;
+ }
+
+ /**
+ * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
+ * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
+ */
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
+ return mTaskbarContainerView;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ @Override
+ public Rect getFolderBoundingBox() {
+ return mTaskbarContainerView.getFolderBoundingBox();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 3b361c4..ddd0d15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -19,19 +19,29 @@
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
/**
* Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
*/
-public class TaskbarContainerView extends FrameLayout {
+public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
+
+ private final int[] mTempLoc = new int[2];
+ private final int mFolderMargin;
+
+ // Initialized in TaskbarController constructor.
+ private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
// Initialized in init.
private TaskbarView mTaskbarView;
@@ -52,12 +62,23 @@
public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
+ super(context, attrs, 1 /* alphaChannelCount */);
+ mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+ }
+
+ protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
}
protected void init(TaskbarView taskbarView) {
mTaskbarView = taskbarView;
mTaskbarInsetsComputer = createTaskbarInsetsComputer();
+ recreateControllers();
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[0];
}
private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
@@ -70,6 +91,17 @@
// We're visible again, accept touches anywhere in our bounds.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
}
+
+ // TaskbarContainerView provides insets to other apps based on contentInsets. These
+ // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+ // to show a floating view like Folder. Thus, we set the contentInsets to be where
+ // mTaskbarView is, since its position never changes and insets rather than overlays.
+ int[] loc = mTempLoc;
+ mTaskbarView.getLocationInWindow(loc);
+ insetsInfo.contentInsets.left = loc[0];
+ insetsInfo.contentInsets.top = loc[1];
+ insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
+ insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight());
};
}
@@ -91,4 +123,30 @@
cleanup();
}
+
+ @Override
+ protected boolean canFindActiveController() {
+ // Unlike super class, we want to be able to find controllers when touches occur in the
+ // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+ return true;
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mControllerCallbacks.onViewRemoved();
+ }
+
+ /**
+ * @return Bounds (in our coordinates) where an opened Folder can display.
+ */
+ protected Rect getFolderBoundingBox() {
+ Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+ boundingBox.inset(mFolderMargin, mFolderMargin);
+ return boundingBox;
+ }
+
+ protected TaskbarActivityContext getTaskbarActivityContext() {
+ return mActivity;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 260428d..ab05fbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -34,11 +34,15 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.ItemClickHandler;
@@ -81,8 +85,9 @@
TaskbarContainerView taskbarContainerView) {
mLauncher = launcher;
mTaskbarContainerView = taskbarContainerView;
+ mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
- mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
+ mTaskbarView.construct(createTaskbarViewCallbacks());
mWindowManager = mLauncher.getWindowManager();
mTaskbarSize = new Point(MATCH_PARENT,
mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
@@ -110,6 +115,18 @@
};
}
+ private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() {
+ return new TaskbarContainerViewCallbacks() {
+ @Override
+ public void onViewRemoved() {
+ if (mTaskbarContainerView.getChildCount() == 1) {
+ // Only TaskbarView remains.
+ setTaskbarWindowFullscreen(false);
+ }
+ }
+ };
+ }
+
private TaskbarViewCallbacks createTaskbarViewCallbacks() {
return new TaskbarViewCallbacks() {
@Override
@@ -120,9 +137,29 @@
Task task = (Task) tag;
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
+ } else if (tag instanceof FolderInfo) {
+ FolderIcon folderIcon = (FolderIcon) view;
+ Folder folder = folderIcon.getFolder();
+
+ setTaskbarWindowFullscreen(true);
+
+ mTaskbarContainerView.post(() -> {
+ folder.animateOpen();
+
+ folder.iterateOverItems((itemInfo, itemView) -> {
+ itemView.setOnClickListener(getItemOnClickListener());
+ itemView.setOnLongClickListener(getItemOnLongClickListener());
+ // To play haptic when dragging, like other Taskbar items do.
+ itemView.setHapticFeedbackEnabled(true);
+ return false;
+ });
+ });
} else {
ItemClickHandler.INSTANCE.onClick(view);
}
+
+ AbstractFloatingView.closeAllOpenViews(
+ mTaskbarContainerView.getTaskbarActivityContext());
};
}
@@ -345,6 +382,20 @@
}
/**
+ * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+ */
+ private void setTaskbarWindowFullscreen(boolean fullscreen) {
+ if (fullscreen) {
+ mWindowLayoutParams.width = MATCH_PARENT;
+ mWindowLayoutParams.height = MATCH_PARENT;
+ } else {
+ mWindowLayoutParams.width = mTaskbarSize.x;
+ mWindowLayoutParams.height = mTaskbarSize.y;
+ }
+ mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
+ }
+
+ /**
* Contains methods that TaskbarStateHandler can call to interface with TaskbarController.
*/
protected interface TaskbarStateHandlerCallbacks {
@@ -361,6 +412,13 @@
}
/**
+ * Contains methods that TaskbarContainerView can call to interface with TaskbarController.
+ */
+ protected interface TaskbarContainerViewCallbacks {
+ void onViewRemoved();
+ }
+
+ /**
* Contains methods that TaskbarView can call to interface with TaskbarController.
*/
protected interface TaskbarViewCallbacks {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d8f3bb5..7a13b89 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -17,12 +17,12 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.DragEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -32,16 +32,20 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
import com.android.systemui.shared.recents.model.Task;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
private final ColorDrawable mBackgroundDrawable;
private final int mItemMarginLeftRight;
@@ -51,6 +55,9 @@
private final RectF mDelegateSlopBounds = new RectF();
private final int[] mTempOutLocation = new int[2];
+ // Initialized in TaskbarController constructor.
+ private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
// Initialized in init().
private int mHotseatStartIndex;
private int mHotseatEndIndex;
@@ -58,13 +65,13 @@
private int mRecentsStartIndex;
private int mRecentsEndIndex;
- private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
-
// Delegate touches to the closest view if within mIconTouchSize.
private boolean mDelegateTargeted;
private View mDelegateView;
private boolean mIsDraggingItem;
+ // Only non-null when the corresponding Folder is open.
+ private @Nullable FolderIcon mLeaveBehindFolderIcon;
public TaskbarView(@NonNull Context context) {
this(context, null);
@@ -90,7 +97,7 @@
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
- protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+ protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
mControllerCallbacks = taskbarViewCallbacks;
}
@@ -130,17 +137,37 @@
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
+ boolean isFolder = false;
+ boolean needsReinflate = false;
if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+ } else if (hotseatItemInfo instanceof FolderInfo) {
+ expectedLayoutResId = R.layout.folder_icon;
+ isFolder = true;
+ // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
+ // if the info changes we need to reinflate. This should only happen if a new folder
+ // is dragged to the position that another folder previously existed.
+ needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
} else {
expectedLayoutResId = R.layout.taskbar_app_icon;
}
- if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+ if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+ || needsReinflate) {
removeView(hotseatView);
- BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
- LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+ TaskbarActivityContext activityContext =
+ ActivityContext.lookupContext(getContext());
+ if (isFolder) {
+ FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+ FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
+ activityContext, this, folderInfo);
+ folderIcon.setTextVisible(false);
+ hotseatView = folderIcon;
+ } else {
+ hotseatView = inflate(expectedLayoutResId);
+ }
+ int iconSize = activityContext.getDeviceProfile().iconSizePx;
+ LayoutParams lp = new LayoutParams(iconSize, iconSize);
lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
- hotseatView = btv;
addView(hotseatView, hotseatIndex, lp);
}
@@ -153,6 +180,11 @@
hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
hotseatView.setOnLongClickListener(
mControllerCallbacks.getItemOnLongClickListener());
+ } else if (isFolder) {
+ hotseatView.setVisibility(VISIBLE);
+ hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+ hotseatView.setOnLongClickListener(
+ mControllerCallbacks.getItemOnLongClickListener());
} else {
hotseatView.setVisibility(GONE);
hotseatView.setOnClickListener(null);
@@ -345,6 +377,7 @@
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
mIsDraggingItem = true;
+ AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext()));
return true;
case DragEvent.ACTION_DRAG_ENDED:
mIsDraggingItem = false;
@@ -357,7 +390,35 @@
return mIsDraggingItem;
}
+ // FolderIconParent implemented methods.
+
+ @Override
+ public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+ mLeaveBehindFolderIcon = child;
+ invalidate();
+ }
+
+ @Override
+ public void clearFolderLeaveBehind(FolderIcon child) {
+ mLeaveBehindFolderIcon = null;
+ invalidate();
+ }
+
+ // End FolderIconParent implemented methods.
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mLeaveBehindFolderIcon != null) {
+ canvas.save();
+ canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
+ mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas);
+ canvas.restore();
+ }
+ }
+
private View inflate(@LayoutRes int layoutResId) {
- return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+ TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
+ return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 2d50125..98551fb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
import static com.android.launcher3.graphics.IconShape.getShape;
import android.content.Context;
@@ -29,7 +28,6 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.graphics.ColorUtils;
@@ -37,9 +35,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
@@ -53,8 +49,7 @@
/**
* A BubbleTextView with a ring around it's drawable
*/
-public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
- LauncherAccessibilityDelegate.AccessibilityActionHandler {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
@@ -148,29 +143,6 @@
}
@Override
- public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
- if (!mIsPinned) {
- accessibilityNodeInfo.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
- getContext().getText(R.string.pin_prediction)));
- }
- }
-
- @Override
- public boolean performAccessibilityAction(int action, ItemInfo info) {
- QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
- if (action == PIN_PREDICTION) {
- if (launcher == null) {
- return false;
- }
- HotseatPredictionController controller = launcher.getHotseatPredictionController();
- controller.pinPrediction(info);
- return true;
- }
- return false;
- }
-
- @Override
public void getIconBounds(Rect outBounds) {
super.getIconBounds(outBounds);
if (!mIsPinned && !mIsDrawingDot) {
@@ -179,6 +151,10 @@
}
}
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
private int getOutlineOffsetX() {
return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d98e792..55dde45 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -43,7 +43,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAccessibilityDelegate;
import com.android.launcher3.Workspace;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
@@ -134,6 +136,11 @@
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
+ @Override
+ protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+ return new QuickstepAccessibilityDelegate(this);
+ }
+
/**
* Returns Prediction controller for hybrid hotseat
*/
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4301377..f99b7e6 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,14 +17,16 @@
import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_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_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -44,6 +46,7 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserManager;
@@ -57,11 +60,11 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayHolder;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.SecureSettingsObserver;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -177,33 +180,33 @@
}
}
+ SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
- SecureSettingsObserver oneHandedEnabledObserver =
- SecureSettingsObserver.newOneHandedSettingsObserver(
- mContext, enabled -> mIsOneHandedModeEnabled = enabled);
- oneHandedEnabledObserver.register();
- oneHandedEnabledObserver.dispatchOnChange();
- runOnDestroy(oneHandedEnabledObserver::unregister);
+ Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
+ SettingsCache.OnChangeListener onChangeListener =
+ enabled -> mIsOneHandedModeEnabled = enabled;
+ settingsCache.register(oneHandedUri, onChangeListener);
+ settingsCache.dispatchOnChange(oneHandedUri);
+ runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
- SecureSettingsObserver swipeBottomEnabledObserver =
- SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
- mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
- swipeBottomEnabledObserver.register();
- swipeBottomEnabledObserver.dispatchOnChange();
- runOnDestroy(swipeBottomEnabledObserver::unregister);
- SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
- context.getContentResolver(),
- e -> mIsUserSetupComplete = e,
- Settings.Secure.USER_SETUP_COMPLETE,
- 0);
- mIsUserSetupComplete = userSetupObserver.getValue();
+ Uri swipeBottomNotificationUri =
+ Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
+ SettingsCache.OnChangeListener onChangeListener =
+ enabled -> mIsSwipeToNotificationEnabled = enabled;
+ settingsCache.register(swipeBottomNotificationUri, onChangeListener);
+ settingsCache.dispatchOnChange(swipeBottomNotificationUri);
+ runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+
+ Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+ mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
if (!mIsUserSetupComplete) {
- userSetupObserver.register();
- runOnDestroy(userSetupObserver::unregister);
+ SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
+ settingsCache.register(setupCompleteUri, userSetupChangeListener);
+ runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
}
}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 3157865..f336bf5 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -28,7 +28,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED;
import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import android.content.Context;
import android.content.SharedPreferences;
@@ -43,7 +43,7 @@
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -77,11 +77,10 @@
getPrefs(context).registerOnSharedPreferenceChangeListener(this);
getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
- SecureSettingsObserver dotsObserver =
- newNotificationSettingsObserver(context, this::onNotificationDotsChanged);
- mNotificationDotsEnabled = dotsObserver.getValue();
- dispatchUserEvent();
-
+ SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ this::onNotificationDotsChanged);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
}
private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a3ee912..215f05a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,22 +23,18 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static com.android.launcher3.Utilities.newContentObserver;
+import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -51,6 +47,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.WindowBounds;
@@ -72,9 +69,6 @@
private static final String TAG = "RecentsOrientedState";
private static final boolean DEBUG = false;
- private ContentObserver mSystemAutoRotateObserver =
- newContentObserver(new Handler(), t -> updateAutoRotateSetting());
-
@Retention(SOURCE)
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
@@ -118,9 +112,11 @@
| FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
- private final ContentResolver mContentResolver;
private final SharedPreferences mSharedPrefs;
private final OrientationEventListener mOrientationListener;
+ private final SettingsCache mSettingsCache;
+ private final SettingsCache.OnChangeListener mRotationChangeListener =
+ isEnabled -> updateAutoRotateSetting();
private final Matrix mTmpMatrix = new Matrix();
@@ -138,7 +134,6 @@
public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
IntConsumer rotationChangeListener) {
mContext = context;
- mContentResolver = context.getContentResolver();
mSharedPrefs = Utilities.getPrefs(context);
mOrientationListener = new OrientationEventListener(context) {
@Override
@@ -162,6 +157,7 @@
mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
}
mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
+ mSettingsCache = SettingsCache.INSTANCE.get(mContext);
initFlags();
}
@@ -271,8 +267,8 @@
}
private void updateAutoRotateSetting() {
- setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
- Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
+ setFlag(FLAG_SYSTEM_ROTATION_ALLOWED,
+ mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
}
private void updateHomeRotationSetting() {
@@ -295,9 +291,7 @@
public void initListeners() {
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
- mContentResolver.registerContentObserver(
- Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
- false, mSystemAutoRotateObserver);
+ mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
}
initFlags();
}
@@ -308,7 +302,7 @@
public void destroyListeners() {
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
- mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+ mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
}
setRotationWatcherEnabled(false);
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 0f6671d..35383d2 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -168,7 +168,7 @@
Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
- "cmd overlay enable-exclusive " + overlayPackage);
+ "cmd overlay enable-exclusive --category " + overlayPackage);
if (currentSysUiNavigationMode() != expectedMode) {
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/res/drawable/ic_widget_height_decrease.xml b/res/drawable/ic_widget_height_decrease.xml
new file mode 100644
index 0000000..df704ba
--- /dev/null
+++ b/res/drawable/ic_widget_height_decrease.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2021 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"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M8,19h3v3h2v-3h3l-4,-4 -4,4zM16,4h-3L13,1h-2v3L8,4l4,4 4,-4zM4,9v2h16L20,9L4,9zM4,12h16v2H4z"/>
+</vector>
diff --git a/res/drawable/ic_widget_height_increase.xml b/res/drawable/ic_widget_height_increase.xml
new file mode 100644
index 0000000..c263a4b
--- /dev/null
+++ b/res/drawable/ic_widget_height_increase.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2021 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"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M4,20h16v2L4,22zM4,2h16v2L4,4zM13,9h3l-4,-4 -4,4h3v6L8,15l4,4 4,-4h-3z"/>
+</vector>
diff --git a/res/drawable/ic_widget_width_decrease.xml b/res/drawable/ic_widget_width_decrease.xml
new file mode 100644
index 0000000..2a2fad7
--- /dev/null
+++ b/res/drawable/ic_widget_width_decrease.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2021 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.
+-->
+<rotate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_widget_height_decrease"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fromDegrees="90"
+ android:toDegrees="90" />
diff --git a/res/drawable/ic_widget_width_increase.xml b/res/drawable/ic_widget_width_increase.xml
new file mode 100644
index 0000000..89b9f40
--- /dev/null
+++ b/res/drawable/ic_widget_width_increase.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2021 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.
+-->
+<rotate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_widget_height_increase"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fromDegrees="90"
+ android:toDegrees="90" />
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 923352e..c230dad 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -27,8 +27,8 @@
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
android:paddingTop="16dp"
launcher:pageIndicator="@+id/folder_page_indicator" />
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index b8600a6..6baf39e 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -17,17 +17,17 @@
*/
-->
<resources>
- <color name="popup_color_primary_light">@android:color/system_main_50</color>
- <color name="popup_color_secondary_light">@android:color/system_main_100</color>
- <color name="popup_color_tertiary_light">@android:color/system_main_300</color>
- <color name="popup_color_primary_dark">@android:color/system_main_800</color>
- <color name="popup_color_secondary_dark">@android:color/system_main_900</color>
- <color name="popup_color_tertiary_dark">@android:color/system_main_700</color>
+ <color name="popup_color_primary_light">@android:color/system_primary_50</color>
+ <color name="popup_color_secondary_light">@android:color/system_primary_100</color>
+ <color name="popup_color_tertiary_light">@android:color/system_primary_300</color>
+ <color name="popup_color_primary_dark">@android:color/system_primary_800</color>
+ <color name="popup_color_secondary_dark">@android:color/system_primary_900</color>
+ <color name="popup_color_tertiary_dark">@android:color/system_primary_700</color>
- <color name="workspace_text_color_light">@android:color/system_main_50</color>
- <color name="workspace_text_color_dark">@android:color/system_main_900</color>
+ <color name="workspace_text_color_light">@android:color/system_primary_50</color>
+ <color name="workspace_text_color_dark">@android:color/system_primary_900</color>
- <color name="text_color_primary_dark">@android:color/system_main_50</color>
- <color name="text_color_secondary_dark">@android:color/system_main_200</color>
- <color name="text_color_tertiary_dark">@android:color/system_main_400</color>
+ <color name="text_color_primary_dark">@android:color/system_primary_50</color>
+ <color name="text_color_secondary_dark">@android:color/system_primary_200</color>
+ <color name="text_color_tertiary_dark">@android:color/system_primary_400</color>
</resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1ce7840..84dfca5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -149,9 +149,9 @@
<dimen name="folder_cell_x_padding">9dp</dimen>
<dimen name="folder_cell_y_padding">6dp</dimen>
<dimen name="folder_child_text_size">13sp</dimen>
- <dimen name="folder_label_padding_top">4dp</dimen>
+ <dimen name="folder_label_padding_top">12dp</dimen>
<dimen name="folder_label_padding_bottom">12dp</dimen>
- <dimen name="folder_label_text_size">14sp</dimen>
+ <dimen name="folder_label_text_size">16sp</dimen>
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c30019b..5a9def7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,8 +37,6 @@
<string name="shortcut_not_available">Shortcut isn\'t available</string>
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
- <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
- <string name="custom_actions">Custom actions</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
new file mode 100644
index 0000000..fbf4c63
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+public class SettingsCacheTest {
+
+ public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
+ public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
+
+ private SettingsCache.OnChangeListener mChangeListener;
+ private SettingsCache mSettingsCache;
+
+ @Before
+ public void setup() {
+ mChangeListener = mock(SettingsCache.OnChangeListener.class);
+ Context targetContext = RuntimeEnvironment.application;
+ mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
+ }
+
+ @Test
+ public void listenerCalledOnChange() {
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void getValueRespectsDefaultValue() {
+ // Case of key not found
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertFalse(val);
+ }
+
+ @Test
+ public void getValueHitsCache() {
+ mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertTrue(val);
+ }
+
+ @Test
+ public void getValueUpdatedCache() {
+ // First ensure there's nothing in cache
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertFalse(val);
+
+ mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+ val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertTrue(val);
+ }
+
+ @Test
+ public void multipleListenersSingleKey() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ verify(secondListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void singleListenerMultipleKeys() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ verify(secondListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void sameListenerMultipleKeys() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+ verify(mChangeListener, times(2)).onSettingsChanged(true);
+ verify(secondListener, times(0)).onSettingsChanged(true);
+ }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 6037c96..95cdbdd 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,7 +59,7 @@
TYPE_SNACKBAR,
TYPE_LISTENER,
TYPE_ALL_APPS_EDU,
-
+ TYPE_DRAG_DROP_POPUP,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 8a03fac..062ab71 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -31,7 +31,6 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import androidx.annotation.IntDef;
@@ -333,7 +332,7 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
- } else if (context instanceof ContextThemeWrapper) {
+ } else if (context instanceof ContextWrapper) {
return fromContext(((ContextWrapper) context).getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index edc7e9b..e5a4335 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -505,7 +505,7 @@
* @param canvas The canvas to draw to.
*/
protected void drawDotIfNecessary(Canvas canvas) {
- if (mDisplay == DISPLAY_TASKBAR) {
+ if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) {
// TODO: support notification dots in Taskbar
return;
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 29812fd..947388b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -19,6 +19,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -309,6 +310,8 @@
setImportantForAccessibility(accessibilityFlag);
getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
+ // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
+ setFocusable(delegate != null);
// Invalidate the accessibility hierarchy
if (getParent() != null) {
getParent().notifySubtreeAccessibilityStateChanged(
@@ -597,7 +600,9 @@
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
- bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
+ if (ENABLE_FOUR_COLUMNS.get()) {
+ bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
+ }
}
child.setScaleX(mChildScale);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 19915b7..9992fde 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -375,7 +375,7 @@
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
- private void updateIconSize(float scale, Resources res) {
+ public void updateIconSize(float scale, Resources res) {
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
@@ -648,8 +648,13 @@
public boolean updateIsSeascape(Context context) {
if (isVerticalBarLayout()) {
// Check an up-to-date info.
- boolean isSeascape = DisplayController.getDefaultDisplay(context)
- .createInfoForContext(context).rotation == Surface.ROTATION_270;
+ DisplayController.Info displayInfo = DisplayController.getDefaultDisplay(context)
+ .createInfoForContext(context);
+ if (displayInfo == null) {
+ return false;
+ }
+
+ boolean isSeascape = displayInfo.rotation == Surface.ROTATION_270;
if (mIsSeascape != isSeascape) {
mIsSeascape = isSeascape;
return true;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 51e7c7d..49adf1f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -37,6 +37,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -74,6 +75,7 @@
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -106,6 +108,7 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -122,7 +125,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.FileLog;
@@ -167,7 +169,6 @@
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;
import com.android.launcher3.util.Thunk;
@@ -394,7 +395,7 @@
idp.addOnChangeListener(this);
mSharedPrefs = Utilities.getPrefs(this);
mIconCache = app.getIconCache();
- mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
+ mAccessibilityDelegate = createAccessibilityDelegate();
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
@@ -1797,6 +1798,43 @@
return newFolder;
}
+ @Override
+ public Rect getFolderBoundingBox() {
+ // We need to bound the folder to the currently visible workspace area
+ Rect folderBoundingBox = new Rect();
+ getWorkspace().getPageAreaRelativeToDragLayer(folderBoundingBox);
+ return folderBoundingBox;
+ }
+
+ @Override
+ public void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+ int left = inOutPosition[0];
+ int top = inOutPosition[1];
+ DeviceProfile grid = getDeviceProfile();
+ int distFromEdgeOfScreen = getWorkspace().getPaddingLeft();
+ if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
+ // Center the folder if it is very close to being centered anyway, by virtue of
+ // filling the majority of the viewport. ie. remove it from the uncanny valley
+ // of centeredness.
+ left = (grid.availableWidthPx - width) / 2;
+ } else if (width >= bounds.width()) {
+ // If the folder doesn't fit within the bounds, center it about the desired bounds
+ left = bounds.left + (bounds.width() - width) / 2;
+ }
+ if (height >= bounds.height()) {
+ // Folder height is greater than page height, center on page
+ top = bounds.top + (bounds.height() - height) / 2;
+ } else {
+ // Folder height is less than page height, so bound it to the absolute open folder
+ // bounds if necessary
+ Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
+ left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
+ top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
+ }
+ inOutPosition[0] = left;
+ inOutPosition[1] = top;
+ }
+
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
@@ -2656,19 +2694,9 @@
shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
}
- final View currentFocus = getCurrentFocus();
- if (currentFocus != null) {
- if (new CustomActionsPopup(this, currentFocus).canShow()) {
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
- KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
- }
- if (currentFocus.getTag() instanceof ItemInfo
- && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
+ getSupportedActions(this, getCurrentFocus()).forEach(la ->
shortcutInfos.add(new KeyboardShortcutInfo(
- getString(R.string.shortcuts_menu_with_notifications_description),
- KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
- }
- }
+ la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
if (!shortcutInfos.isEmpty()) {
data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
}
@@ -2686,30 +2714,18 @@
return true;
}
break;
- case KeyEvent.KEYCODE_S: {
- View focusedView = getCurrentFocus();
- if (focusedView instanceof BubbleTextView
- && focusedView.getTag() instanceof ItemInfo
- && mAccessibilityDelegate.performAction(focusedView,
- (ItemInfo) focusedView.getTag(),
- LauncherAccessibilityDelegate.DEEP_SHORTCUTS,
- true)) {
- PopupContainerWithArrow.getOpen(this).requestFocus();
- return true;
- }
- break;
- }
- case KeyEvent.KEYCODE_O:
- if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
- return true;
- }
- break;
case KeyEvent.KEYCODE_W:
if (isInState(NORMAL)) {
OptionsPopupView.openWidgets(this);
return true;
}
break;
+ default:
+ for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
+ if (la.keyCode == keyCode) {
+ return la.invokeFromKeyboard(getCurrentFocus());
+ }
+ }
}
}
return super.onKeyShortcut(keyCode, event);
@@ -2767,6 +2783,9 @@
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
+ protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+ return new LauncherAccessibilityDelegate(this);
+ }
/**
* @see LauncherState#getOverviewScaleAndOffset(Launcher)
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a4181c5..57d7600 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,8 +17,8 @@
package com.android.launcher3;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -37,10 +37,10 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -57,8 +57,9 @@
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
+ private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
private InstallSessionTracker mInstallSessionTracker;
private SimpleBroadcastReceiver mModelChangeReceiver;
private SafeCloseable mCalendarChangeTracker;
@@ -108,10 +109,11 @@
.registerInstallTracker(mModel);
// Register an observer to rebind the notification listener when dots are re-enabled.
- mNotificationDotsObserver =
- newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
- mNotificationDotsObserver.register();
- mNotificationDotsObserver.dispatchOnChange();
+ mSettingsCache = SettingsCache.INSTANCE.get(mContext);
+ mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
}
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -166,8 +168,9 @@
}
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
- if (mNotificationDotsObserver != null) {
- mNotificationDotsObserver.unregister();
+ if (mSettingsCache != null) {
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
}
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 92b88e6..7276887 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -37,6 +37,8 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
@@ -203,9 +205,13 @@
d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
super.onDrop(d, options);
- StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId);
- if (d.originalDragInfo != null) {
- logger.withItemInfo(d.originalDragInfo);
+ doLog(d.logInstanceId, d.originalDragInfo);
+ }
+
+ private void doLog(InstanceId logInstanceId, ItemInfo itemInfo) {
+ StatsLogger logger = mStatsLogManager.logger().withInstanceId(logInstanceId);
+ if (itemInfo != null) {
+ logger.withItemInfo(itemInfo);
}
if (mCurrentAccessibilityAction == UNINSTALL) {
logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
@@ -296,6 +302,7 @@
@Override
public void onAccessibilityDrop(View view, ItemInfo item) {
+ doLog(new InstanceIdSequence().newInstanceId(), item);
performDropAction(view, item);
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 1c5081c..eab8272 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -26,10 +26,11 @@
import android.view.ViewGroup;
import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-public class ShortcutAndWidgetContainer extends ViewGroup {
+public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
static final String TAG = "ShortcutAndWidgetContainer";
// These are temporary variables to prevent having to allocate a new object just to
@@ -228,4 +229,24 @@
child.cancelLongPress();
}
}
+
+ @Override
+ public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ // While the folder is open, the position of the icon cannot change.
+ lp.canReorder = false;
+ if (mContainerType == CellLayout.HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+ }
+
+ @Override
+ public void clearFolderLeaveBehind(FolderIcon child) {
+ ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
+ if (mContainerType == CellLayout.HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent();
+ cl.clearFolderLeaveBehind();
+ }
+ }
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 51d8e66..c440303 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -605,7 +605,6 @@
outObj[0] = activityInfo;
return activityInfo.getFullResIcon(appState.getIconCache());
}
- if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
.buildRequest(launcher)
.query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6fac79a..cd4616a 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -3,17 +3,18 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
-import android.app.AlertDialog;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.DialogInterface;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -25,7 +26,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
@@ -33,7 +33,6 @@
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
@@ -41,14 +40,19 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.views.OptionsPopupView.OptionItem;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
@@ -78,89 +82,105 @@
public View item;
}
- protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
- @Thunk final Launcher mLauncher;
+ protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
+ protected final Launcher mLauncher;
private DragInfo mDragInfo = null;
public LauncherAccessibilityDelegate(Launcher launcher) {
mLauncher = launcher;
- mActions.put(REMOVE, new AccessibilityAction(REMOVE,
- launcher.getText(R.string.remove_drop_target_label)));
- mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
- launcher.getText(R.string.uninstall_drop_target_label)));
- mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
- launcher.getText(R.string.dismiss_prediction_label)));
- mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
- launcher.getText(R.string.gadget_setup_text)));
- mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
- launcher.getText(R.string.action_add_to_workspace)));
- mActions.put(MOVE, new AccessibilityAction(MOVE,
- launcher.getText(R.string.action_move)));
- mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
- launcher.getText(R.string.action_move_to_workspace)));
- mActions.put(RESIZE, new AccessibilityAction(RESIZE,
- launcher.getText(R.string.action_resize)));
- mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS,
- launcher.getText(R.string.action_deep_shortcut)));
- mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new AccessibilityAction(DEEP_SHORTCUTS,
- launcher.getText(R.string.shortcuts_menu_with_notifications_description)));
+ mActions.put(REMOVE, new LauncherAction(
+ REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X));
+ mActions.put(UNINSTALL, new LauncherAction(
+ UNINSTALL, R.string.uninstall_drop_target_label, KeyEvent.KEYCODE_U));
+ mActions.put(DISMISS_PREDICTION, new LauncherAction(DISMISS_PREDICTION,
+ R.string.dismiss_prediction_label, KeyEvent.KEYCODE_X));
+ mActions.put(RECONFIGURE, new LauncherAction(
+ RECONFIGURE, R.string.gadget_setup_text, KeyEvent.KEYCODE_E));
+ mActions.put(ADD_TO_WORKSPACE, new LauncherAction(
+ ADD_TO_WORKSPACE, R.string.action_add_to_workspace, KeyEvent.KEYCODE_P));
+ mActions.put(MOVE, new LauncherAction(
+ MOVE, R.string.action_move, KeyEvent.KEYCODE_M));
+ mActions.put(MOVE_TO_WORKSPACE, new LauncherAction(MOVE_TO_WORKSPACE,
+ R.string.action_move_to_workspace, KeyEvent.KEYCODE_P));
+ mActions.put(RESIZE, new LauncherAction(
+ RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R));
+ mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS,
+ R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
+ mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new LauncherAction(DEEP_SHORTCUTS,
+ R.string.shortcuts_menu_with_notifications_description, KeyEvent.KEYCODE_S));
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- addSupportedActions(host, info, false);
+ if (host.getTag() instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) host.getTag();
+
+ List<LauncherAction> actions = new ArrayList<>();
+ getSupportedActions(host, item, actions);
+ actions.forEach(la -> info.addAction(la.accessibilityAction));
+
+ if (!itemSupportsLongClick(host, item)) {
+ info.setLongClickable(false);
+ info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
+ }
+ }
}
- public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
- if (!(host.getTag() instanceof ItemInfo)) return;
- ItemInfo item = (ItemInfo) host.getTag();
-
- if (host instanceof AccessibilityActionHandler) {
- ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info);
- }
-
+ /**
+ * Adds all the accessibility actions that can be handled.
+ */
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
// If the request came from keyboard, do not add custom shortcuts as that is already
// exposed as a direct shortcut
- if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
- info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null
+ if (ShortcutUtil.supportsShortcuts(item)) {
+ out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null
? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
}
for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
if (target.supportsAccessibilityDrop(item, host)) {
- info.addAction(mActions.get(target.getAccessibilityAction()));
+ out.add(mActions.get(target.getAccessibilityAction()));
}
}
// Do not add move actions for keyboard request as this uses virtual nodes.
if (itemSupportsAccessibleDrag(item)) {
- info.addAction(mActions.get(MOVE));
+ out.add(mActions.get(MOVE));
if (item.container >= 0) {
- info.addAction(mActions.get(MOVE_TO_WORKSPACE));
+ out.add(mActions.get(MOVE_TO_WORKSPACE));
} else if (item instanceof LauncherAppWidgetInfo) {
if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
- info.addAction(mActions.get(RESIZE));
+ out.add(mActions.get(RESIZE));
}
}
}
- if (!fromKeyboard && !itemSupportsLongClick(host, item)) {
- info.setLongClickable(false);
- info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
- }
-
if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ out.add(mActions.get(ADD_TO_WORKSPACE));
}
}
+ /**
+ * Returns all the accessibility actions that can be handled by the host.
+ */
+ public static List<LauncherAction> getSupportedActions(Launcher launcher, View host) {
+ if (host == null || !(host.getTag() instanceof ItemInfo)) {
+ return Collections.emptyList();
+ }
+ PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
+ LauncherAccessibilityDelegate delegate = container != null
+ ? container.getAccessibilityDelegate() : launcher.getAccessibilityDelegate();
+ List<LauncherAction> result = new ArrayList<>();
+ delegate.getSupportedActions(host, (ItemInfo) host.getTag(), result);
+ return result;
+ }
+
private boolean itemSupportsLongClick(View host, ItemInfo info) {
- return PopupContainerWithArrow.canShow(host, info)
- || new CustomActionsPopup(mLauncher, host).canShow();
+ return PopupContainerWithArrow.canShow(host, info);
}
private boolean itemSupportsAccessibleDrag(ItemInfo item) {
@@ -184,7 +204,7 @@
/**
* Performs the provided action on the host
*/
- public boolean performAction(final View host, final ItemInfo item, int action,
+ protected boolean performAction(final View host, final ItemInfo item, int action,
boolean fromKeyboard) {
if (action == ACTION_LONG_CLICK) {
if (PopupContainerWithArrow.canShow(host, item)) {
@@ -193,19 +213,8 @@
// standard long press path does.
PopupContainerWithArrow.showForIcon((BubbleTextView) host);
return true;
- } else {
- CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
- if (popup.canShow()) {
- popup.show();
- return true;
- }
}
- }
- if (host instanceof AccessibilityActionHandler
- && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) {
- return true;
- }
- if (action == MOVE) {
+ } else if (action == MOVE) {
return beginAccessibleDrag(host, item, fromKeyboard);
} else if (action == ADD_TO_WORKSPACE) {
final int[] coordinates = new int[2];
@@ -220,9 +229,7 @@
Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(info);
- mLauncher.bindItems(itemList, true);
+ mLauncher.bindItems(Collections.singletonList(info), true);
announceConfirmation(R.string.item_added_to_workspace);
} else if (item instanceof PendingAddItemInfo) {
PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -243,47 +250,31 @@
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
mLauncher.getModelWriter().moveItemInDatabase(info,
- LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
// Bind the item in next frame so that if a new workspace page was created,
// it will get laid out.
- new Handler().post(new Runnable() {
-
- @Override
- public void run() {
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(item);
- mLauncher.bindItems(itemList, true);
- announceConfirmation(R.string.item_moved);
- }
+ new Handler().post(() -> {
+ mLauncher.bindItems(Collections.singletonList(item), true);
+ announceConfirmation(R.string.item_moved);
});
+ return true;
} else if (action == RESIZE) {
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
- final IntArray actions = getSupportedResizeActions(host, info);
- CharSequence[] labels = new CharSequence[actions.size()];
- for (int i = 0; i < actions.size(); i++) {
- labels[i] = mLauncher.getText(actions.get(i));
- }
-
- new AlertDialog.Builder(mLauncher)
- .setTitle(R.string.action_resize)
- .setItems(labels, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- performResizeAction(actions.get(which), host, info);
- dialog.dismiss();
- }
- })
- .show();
+ List<OptionItem> actions = getSupportedResizeActions(host, info);
+ Rect pos = new Rect();
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
+ ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions);
+ popup.requestFocus();
+ popup.setOnCloseCallback(host::requestFocus);
return true;
- } else if (action == DEEP_SHORTCUTS) {
+ } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
} else {
for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
- if (dropTarget.supportsAccessibilityDrop(item, host) &&
- action == dropTarget.getAccessibilityAction()) {
+ if (dropTarget.supportsAccessibilityDrop(item, host)
+ && action == dropTarget.getAccessibilityAction()) {
dropTarget.onAccessibilityDrop(host, item);
return true;
}
@@ -292,9 +283,8 @@
return false;
}
- private IntArray getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
- IntArray actions = new IntArray();
-
+ private List<OptionItem> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
+ List<OptionItem> actions = new ArrayList<>();
AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
if (providerInfo == null) {
return actions;
@@ -304,28 +294,40 @@
if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
- actions.add(R.string.action_increase_width);
+ actions.add(new OptionItem(
+ R.string.action_increase_width, R.drawable.ic_widget_width_increase,
+ IGNORE,
+ v -> performResizeAction(R.string.action_increase_width, host, info)));
}
if (info.spanX > info.minSpanX && info.spanX > 1) {
- actions.add(R.string.action_decrease_width);
+ actions.add(new OptionItem(
+ R.string.action_decrease_width, R.drawable.ic_widget_width_decrease,
+ IGNORE,
+ v -> performResizeAction(R.string.action_decrease_width, host, info)));
}
}
if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
- actions.add(R.string.action_increase_height);
+ actions.add(new OptionItem(
+ R.string.action_increase_height, R.drawable.ic_widget_height_increase,
+ IGNORE,
+ v -> performResizeAction(R.string.action_increase_height, host, info)));
}
if (info.spanY > info.minSpanY && info.spanY > 1) {
- actions.add(R.string.action_decrease_height);
+ actions.add(new OptionItem(
+ R.string.action_decrease_height, R.drawable.ic_widget_height_decrease,
+ IGNORE,
+ v -> performResizeAction(R.string.action_decrease_height, host, info)));
}
}
return actions;
}
- @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
+ private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
CellLayout layout = (CellLayout) host.getParent().getParent();
layout.markCellsAsUnoccupiedForView(host);
@@ -362,6 +364,7 @@
host.requestLayout();
mLauncher.getModelWriter().updateItemInDatabase(info);
announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+ return true;
}
@Thunk void announceConfirmation(int resId) {
@@ -489,19 +492,28 @@
return screenId;
}
- /**
- * An interface allowing views to handle their own action.
- */
- public interface AccessibilityActionHandler {
+ public class LauncherAction {
+ public final int keyCode;
+ public final AccessibilityAction accessibilityAction;
+
+ private final LauncherAccessibilityDelegate mDelegate;
+
+ public LauncherAction(int id, int labelRes, int keyCode) {
+ this.keyCode = keyCode;
+ accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes));
+ mDelegate = LauncherAccessibilityDelegate.this;
+ }
/**
- * performs accessibility action and returns true on success
+ * Invokes the action for the provided host
*/
- boolean performAccessibilityAction(int action, ItemInfo itemInfo);
-
- /**
- * adds all the accessibility actions that can be handled.
- */
- void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo);
+ public boolean invokeFromKeyboard(View host) {
+ if (host != null && host.getTag() instanceof ItemInfo) {
+ return mDelegate.performAction(
+ host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
+ } else {
+ return false;
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index aaaff98..1733e5d 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -18,9 +18,8 @@
import static com.android.launcher3.LauncherState.NORMAL;
+import android.view.KeyEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
@@ -31,7 +30,8 @@
import com.android.launcher3.notification.NotificationMainView;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Extension of {@link LauncherAccessibilityDelegate} with actions specific to shortcuts in
@@ -43,23 +43,23 @@
public ShortcutMenuAccessibilityDelegate(Launcher launcher) {
super(launcher);
- mActions.put(DISMISS_NOTIFICATION, new AccessibilityAction(DISMISS_NOTIFICATION,
- launcher.getText(R.string.action_dismiss_notification)));
+ mActions.put(DISMISS_NOTIFICATION, new LauncherAction(DISMISS_NOTIFICATION,
+ R.string.action_dismiss_notification, KeyEvent.KEYCODE_X));
}
@Override
- public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
if ((host.getParent() instanceof DeepShortcutView)) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ out.add(mActions.get(ADD_TO_WORKSPACE));
} else if (host instanceof NotificationMainView) {
if (((NotificationMainView) host).canChildBeDismissed()) {
- info.addAction(mActions.get(DISMISS_NOTIFICATION));
+ out.add(mActions.get(DISMISS_NOTIFICATION));
}
}
}
@Override
- public boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
+ protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
if (action == ADD_TO_WORKSPACE) {
if (!(host.getParent() instanceof DeepShortcutView)) {
return false;
@@ -73,9 +73,7 @@
mLauncher.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(info);
- mLauncher.bindItems(itemList, true);
+ mLauncher.bindItems(Collections.singletonList(info), true);
AbstractFloatingView.closeAllOpenViews(mLauncher);
announceConfirmation(R.string.item_added_to_workspace);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 769cb5e..355ccad 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -78,6 +80,11 @@
return (mModelFlags & mask) != 0;
}
+ /**
+ * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise
+ * null.
+ */
+ @Nullable
public AppInfo getApp(ComponentKey key) {
mTempInfo.componentName = key.componentName;
mTempInfo.user = key.user;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 74d8dca..504b29e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,6 +46,7 @@
import android.util.Pair;
import android.view.FocusFinder;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
@@ -82,7 +83,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -96,6 +96,8 @@
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -165,7 +167,11 @@
private AnimatorSet mCurrentAnimator;
private boolean mIsAnimatingClosed = false;
+ // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar).
+ // Anything specific to Launcher should use mLauncher, otherwise should use mActivityContext.
protected final Launcher mLauncher;
+ protected final ActivityContext mActivityContext;
+
protected DragController mDragController;
public FolderInfo mInfo;
private CharSequence mFromTitle;
@@ -228,6 +234,7 @@
setAlwaysDrawnWithCacheEnabled(false);
mLauncher = Launcher.getLauncher(context);
+ mActivityContext = ActivityContext.lookupContext(context);
mStatsLogManager = StatsLogManager.newInstance(context);
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -457,9 +464,9 @@
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
- lp = new DragLayer.LayoutParams(0, 0);
+ lp = new BaseDragLayer.LayoutParams(0, 0);
lp.customPosition = true;
setLayoutParams(lp);
}
@@ -513,13 +520,14 @@
/**
* Creates a new UserFolder, inflated from R.layout.user_folder.
*
- * @param launcher The main activity.
+ * @param activityContext The main ActivityContext in which to inflate this Folder. It must also
+ * be an instance or ContextWrapper around the Launcher activity context.
*
* @return A new UserFolder.
*/
@SuppressLint("InflateParams")
- static Folder fromXml(Launcher launcher) {
- return (Folder) launcher.getLayoutInflater()
+ static <T extends Context & ActivityContext> Folder fromXml(T activityContext) {
+ return (Folder) LayoutInflater.from(activityContext).cloneInContext(activityContext)
.inflate(R.layout.user_folder_icon_normalized, null);
}
@@ -597,7 +605,7 @@
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
- Folder openFolder = getOpen(mLauncher);
+ Folder openFolder = getOpen(mActivityContext);
if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder.
openFolder.close(true);
@@ -610,7 +618,7 @@
mIsOpen = true;
- DragLayer dragLayer = mLauncher.getDragLayer();
+ BaseDragLayer dragLayer = mActivityContext.getDragLayer();
// Just verify that the folder hasn't already been added to the DragLayer.
// There was a one-off crash where the folder had a parent already.
if (getParent() == null) {
@@ -724,7 +732,7 @@
// Notify the accessibility manager that this folder "window" has disappeared and no
// longer occludes the workspace items
- mLauncher.getDragLayer().sendAccessibilityEvent(
+ mActivityContext.getDragLayer().sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
@@ -772,7 +780,7 @@
private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations.
- DragLayer parent = (DragLayer) getParent();
+ BaseDragLayer parent = (BaseDragLayer) getParent();
if (parent != null) {
parent.removeView(this);
}
@@ -1011,7 +1019,7 @@
private void updateItemLocationsInDatabaseBatch(boolean isBind) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
- mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+ mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
int total = mInfo.contents.size();
@@ -1048,10 +1056,8 @@
}
private void centerAboutIcon() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
-
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
- DragLayer parent = mLauncher.getDragLayer();
+ BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer parent = mActivityContext.getDragLayer();
int width = getFolderWidth();
int height = getFolderHeight();
@@ -1061,38 +1067,13 @@
int centeredLeft = centerX - width / 2;
int centeredTop = centerY - height / 2;
- // We need to bound the folder to the currently visible workspace area
- if (mLauncher.getStateManager().getState().overviewUi) {
- parent.getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(), sTempRect);
- } else {
- mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
- }
- int left = Math.min(Math.max(sTempRect.left, centeredLeft),
- sTempRect.right- width);
- int top = Math.min(Math.max(sTempRect.top, centeredTop),
- sTempRect.bottom - height);
-
- int distFromEdgeOfScreen = mLauncher.getWorkspace().getPaddingLeft() + getPaddingLeft();
-
- if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
- // Center the folder if it is very close to being centered anyway, by virtue of
- // filling the majority of the viewport. ie. remove it from the uncanny valley
- // of centeredness.
- left = (grid.availableWidthPx - width) / 2;
- } else if (width >= sTempRect.width()) {
- // If the folder doesn't fit within the bounds, center it about the desired bounds
- left = sTempRect.left + (sTempRect.width() - width) / 2;
- }
- if (height >= sTempRect.height()) {
- // Folder height is greater than page height, center on page
- top = sTempRect.top + (sTempRect.height() - height) / 2;
- } else {
- // Folder height is less than page height, so bound it to the absolute open folder
- // bounds if necessary
- Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
- left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
- top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
- }
+ sTempRect.set(mActivityContext.getFolderBoundingBox());
+ int left = Utilities.boundToRange(centeredLeft, sTempRect.left, sTempRect.right - width);
+ int top = Utilities.boundToRange(centeredTop, sTempRect.top, sTempRect.bottom - height);
+ int[] inOutPosition = new int[] {left, top};
+ mActivityContext.updateOpenFolderPosition(inOutPosition, sTempRect, width, height);
+ left = inOutPosition[0];
+ top = inOutPosition[1];
int folderPivotX = width / 2 + (centeredLeft - left);
int folderPivotY = height / 2 + (centeredTop - top);
@@ -1106,7 +1087,7 @@
}
protected int getContentAreaHeight() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
+ DeviceProfile grid = mActivityContext.getDeviceProfile();
int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
- mFooterHeight;
int height = Math.min(maxContentAreaHeight,
@@ -1384,7 +1365,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
- mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+ mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
@@ -1591,8 +1572,8 @@
/**
* Returns a folder which is already open or null
*/
- public static Folder getOpen(Launcher launcher) {
- return getOpenView(launcher, TYPE_FOLDER);
+ public static Folder getOpen(ActivityContext activityContext) {
+ return getOpenView(activityContext, TYPE_FOLDER);
}
/**
@@ -1611,7 +1592,7 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dl = mLauncher.getDragLayer();
+ BaseDragLayer dl = (BaseDragLayer) getParent();
if (isEditingName()) {
if (!dl.isEventOverView(mFolderName, ev)) {
@@ -1635,6 +1616,11 @@
return false;
}
+ @Override
+ public boolean canInterceptEventsInSystemGestureRegion() {
+ return true;
+ }
+
/**
* Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
* rounded rect.
@@ -1663,9 +1649,9 @@
/** Returns the height of the current folder's bottom edge from the bottom of the screen. */
private int getHeightFromBottom() {
- DragLayer.LayoutParams layoutParams = (DragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer.LayoutParams layoutParams = (BaseDragLayer.LayoutParams) getLayoutParams();
int folderBottomPx = layoutParams.y + layoutParams.height;
- int windowBottomPx = mLauncher.getDeviceProfile().heightPx;
+ int windowBottomPx = mActivityContext.getDeviceProfile().heightPx;
return windowBottomPx - folderBottomPx;
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 3d72b49..1cac31e 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -39,14 +39,13 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.BaseDragLayer;
import java.util.List;
@@ -69,7 +68,6 @@
private PreviewBackground mPreviewBackground;
private Context mContext;
- private Launcher mLauncher;
private final boolean mIsOpening;
@@ -92,8 +90,7 @@
mPreviewBackground = mFolderIcon.mBackground;
mContext = folder.getContext();
- mLauncher = folder.mLauncher;
- mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
+ mPreviewVerifier = new FolderGridOrganizer(folder.mActivityContext.getDeviceProfile().inv);
mIsOpening = isOpening;
@@ -114,14 +111,15 @@
* Prepares the Folder for animating between open / closed states.
*/
public AnimatorSet getAnimator() {
- final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
+ final BaseDragLayer.LayoutParams lp =
+ (BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
- float scaleRelativeToDragLayer = mLauncher.getDragLayer()
+ float scaleRelativeToDragLayer = mFolder.mActivityContext.getDragLayer()
.getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
int scaledRadius = mPreviewBackground.getScaledRadius();
float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6477de3..6b02021 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -166,19 +166,19 @@
mDotParams = new DotRenderer.DrawParams();
}
- public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
- FolderInfo folderInfo) {
- Folder folder = Folder.fromXml(launcher);
- folder.setDragController(launcher.getDragController());
+ public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
+ T activityContext, ViewGroup group, FolderInfo folderInfo) {
+ Folder folder = Folder.fromXml(activityContext);
+ folder.setDragController(folder.mLauncher.getDragController());
- FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
+ FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
icon.setFolder(folder);
- icon.setOnFocusChangeListener(launcher.getFocusHandler());
+ icon.setOnFocusChangeListener(folder.mLauncher.getFocusHandler());
icon.mBackground.paddingY = icon.isInHotseat()
- ? 0 : launcher.getDeviceProfile().cellYPaddingPx;
+ ? 0 : activityContext.getDeviceProfile().cellYPaddingPx;
return icon;
}
@@ -754,20 +754,14 @@
}
public void clearLeaveBehindIfExists() {
- ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
- if (isInHotseat()) {
- CellLayout cl = (CellLayout) getParent().getParent();
- cl.clearFolderLeaveBehind();
+ if (getParent() instanceof FolderIconParent) {
+ ((FolderIconParent) getParent()).clearFolderLeaveBehind(this);
}
}
public void drawLeaveBehindIfExists() {
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
- // While the folder is open, the position of the icon cannot change.
- lp.canReorder = false;
- if (isInHotseat()) {
- CellLayout cl = (CellLayout) getParent().getParent();
- cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ if (getParent() instanceof FolderIconParent) {
+ ((FolderIconParent) getParent()).drawFolderLeaveBehindForIcon(this);
}
}
@@ -836,4 +830,19 @@
MAX_NUM_ITEMS_IN_PREVIEW);
}
}
+
+ /**
+ * Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon.
+ */
+ public interface FolderIconParent {
+ /**
+ * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open and leaving a
+ * gap where the FolderIcon would be when the Folder is closed.
+ */
+ void drawFolderLeaveBehindForIcon(FolderIcon child);
+ /**
+ * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder is closed.
+ */
+ void clearFolderLeaveBehind(FolderIcon child);
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index df3e92c..0235dfa 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
@@ -230,7 +229,7 @@
}
private CellLayout createAndAddNewPage() {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = mFolder.mActivityContext.getDeviceProfile();
CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
@@ -624,7 +623,7 @@
@Override
protected boolean canScroll(float absVScroll, float absHScroll) {
- return AbstractFloatingView.getTopOpenViewWithType(mFolder.mLauncher,
+ return AbstractFloatingView.getTopOpenViewWithType(mFolder.mActivityContext,
TYPE_ALL & ~TYPE_FOLDER) == null;
}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
deleted file mode 100644
index 77ce4a8..0000000
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.keyboard;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.PopupMenu;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Handles showing a popup menu with available custom actions for a launcher icon.
- * This allows exposing various custom actions using keyboard shortcuts.
- */
-public class CustomActionsPopup implements OnMenuItemClickListener {
-
- private final Launcher mLauncher;
- private final LauncherAccessibilityDelegate mDelegate;
- private final View mIcon;
-
- public CustomActionsPopup(Launcher launcher, View icon) {
- mLauncher = launcher;
- mIcon = icon;
- PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
- if (container != null) {
- mDelegate = container.getAccessibilityDelegate();
- } else {
- mDelegate = launcher.getAccessibilityDelegate();
- }
- }
-
- private List<AccessibilityAction> getActionList() {
- if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
- return Collections.EMPTY_LIST;
- }
-
- AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
- mDelegate.addSupportedActions(mIcon, info, true);
- List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
- info.recycle();
- return result;
- }
-
- public boolean canShow() {
- return !getActionList().isEmpty();
- }
-
- public boolean show() {
- List<AccessibilityAction> actions = getActionList();
- if (actions.isEmpty()) {
- return false;
- }
-
- PopupMenu popup = new PopupMenu(mLauncher, mIcon);
- popup.setOnMenuItemClickListener(this);
- Menu menu = popup.getMenu();
- for (AccessibilityAction action : actions) {
- menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
- }
- popup.show();
- return true;
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId(),
- true);
- }
-}
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index e3e4b69..a519f92 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -22,7 +22,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.os.LocaleList;
@@ -145,10 +144,9 @@
}
public void addPromiseApp(Context context, PackageInstallInfo installInfo) {
- ApplicationInfo applicationInfo = new PackageManagerHelper(context)
- .getApplicationInfo(installInfo.packageName, installInfo.user, 0);
// only if not yet installed
- if (applicationInfo == null) {
+ if (!new PackageManagerHelper(context)
+ .isAppInstalled(installInfo.packageName, installInfo.user)) {
AppInfo info = new AppInfo(installInfo);
mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
info.sectionName = mIndex.computeSectionName(info.title);
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index e3e8769..434776c 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
+import android.content.Intent;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
@@ -66,8 +66,8 @@
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
- ComponentName cn = si.getTargetComponent();
- if ((cn != null) && cn.getPackageName().equals(mPackageName)) {
+ Intent intent = si.getIntent();
+ if ((intent != null) && mPackageName.equals(intent.getPackage())) {
si.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
si.setProgressLevel(downloadInfo);
updatedWorkspaceItems.add(si);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 8215edd..1380e9e 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -72,9 +72,9 @@
synchronized (dataModel) {
final HashSet<ItemInfo> updates = new HashSet<>();
dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
- ComponentName cn = si.getTargetComponent();
- if (si.hasPromiseIconUi() && (cn != null)
- && cn.getPackageName().equals(mInstallInfo.packageName)) {
+ Intent intent = si.getIntent();
+ if (si.hasPromiseIconUi() && (intent != null)
+ && mInstallInfo.packageName.equals(intent.getPackage())) {
int installProgress = mInstallInfo.progress;
si.setProgressLevel(installProgress, PackageInstallInfo.STATUS_INSTALLING);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index f13a109..7bfa3ef 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -30,6 +30,7 @@
import android.util.Log;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
@@ -228,7 +229,8 @@
isTargetValid = context.getSystemService(LauncherApps.class)
.isActivityEnabled(cn, mUser);
}
- if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
+ if (!isTargetValid && si.hasStatusFlag(
+ FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
if (updateWorkspaceItemIntent(context, si, packageName)) {
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
@@ -250,8 +252,7 @@
}
}
- if (isNewApkAvailable
- && si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
si.setProgressLevel(
@@ -260,8 +261,10 @@
: PackageManagerHelper.getLoadingProgress(
activities.get(0)),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
- iconCache.getTitleAndIcon(si, si.usingLowResIcon());
- infoUpdated = true;
+ if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+ infoUpdated = true;
+ }
}
int oldRuntimeFlags = si.runtimeStatusFlags;
@@ -353,6 +356,11 @@
*/
private boolean updateWorkspaceItemIntent(Context context,
WorkspaceItemInfo si, String packageName) {
+ if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ // Do not update intent for deep shortcuts as they contain additional information
+ // about the shortcut.
+ return false;
+ }
// Try to find the best match activity.
Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
if (intent != null) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 6fedad1..4296d32 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -25,6 +25,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
import java.util.HashSet;
@@ -66,6 +67,14 @@
}
if (!matchingWorkspaceItems.isEmpty()) {
+ if (mShortcuts.isEmpty()) {
+ // Verify that the app is indeed installed.
+ if (!new PackageManagerHelper(app.getContext())
+ .isAppInstalled(mPackageName, mUser)) {
+ // App is not installed, ignoring package events
+ return;
+ }
+ }
// Update the workspace to reflect the changes to updated shortcuts residing on it.
List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
.map(WorkspaceItemInfo::getDeepShortcutId)
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 059ad18..2905dc3 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,9 +16,9 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -37,8 +37,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.SecureSettingsObserver;
import java.util.ArrayList;
import java.util.Arrays;
@@ -81,7 +81,8 @@
/** The last notification key that was dismissed from launcher UI */
private String mLastKeyDismissedByLauncher;
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
+ private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
public NotificationListener() {
mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
@@ -207,10 +208,12 @@
super.onListenerConnected();
sIsConnected = true;
- mNotificationDotsObserver =
- newNotificationSettingsObserver(this, this::onNotificationSettingsChanged);
- mNotificationDotsObserver.register();
- mNotificationDotsObserver.dispatchOnChange();
+ // Register an observer to rebind the notification listener when dots are re-enabled.
+ mSettingsCache = SettingsCache.INSTANCE.get(this);
+ mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
onNotificationFullRefresh();
}
@@ -229,7 +232,7 @@
public void onListenerDisconnected() {
super.onListenerDisconnected();
sIsConnected = false;
- mNotificationDotsObserver.unregister();
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
onNotificationFullRefresh();
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index fa25114..0091af1 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -217,8 +217,8 @@
&& sessionInfo.getAppIcon() != null
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
&& !promiseIconAddedForId(sessionInfo.getSessionId())
- && new PackageManagerHelper(mAppContext).getApplicationInfo(
- sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
+ && !new PackageManagerHelper(mAppContext).isAppInstalled(
+ sessionInfo.getAppPackageName(), getUserHandle(sessionInfo))) {
ItemInstallQueue.INSTANCE.get(mAppContext)
.queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 90285c4..56438d0 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,6 +40,8 @@
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
@@ -82,6 +84,8 @@
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
+ private Runnable mOnCloseCallback = () -> { };
+
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
@@ -555,6 +559,14 @@
mDeferContainerRemoval = false;
getPopupContainer().removeView(this);
getPopupContainer().removeView(mArrow);
+ mOnCloseCallback.run();
+ }
+
+ /**
+ * Callback to be called when the popup is closed
+ */
+ public void setOnCloseCallback(@NonNull Runnable callback) {
+ mOnCloseCallback = callback;
}
protected BaseDragLayer getPopupContainer() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 59930ff..a1ba747 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -206,6 +206,7 @@
.filter(Objects::nonNull)
.collect(Collectors.toList()));
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
+ container.requestFocus();
return container;
}
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index a91303a..a354169 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -35,14 +35,14 @@
import com.android.launcher3.R;
import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
/**
* A {@link Preference} for indicating notification dots status.
* Also has utility methods for updating UI based on dots status changes.
*/
public class NotificationDotsPreference extends Preference
- implements SecureSettingsObserver.OnChangeListener {
+ implements SettingsCache.OnChangeListener {
private boolean mWidgetFrameVisible = false;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 922425f..ac8dac5 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,13 +18,13 @@
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_ENABLED_LISTENERS;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -45,8 +45,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.SecureSettingsObserver;
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -59,8 +59,6 @@
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
- /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
- private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
@@ -126,10 +124,11 @@
*/
public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
private String mHighLightKey;
private boolean mPreferenceHighlighted = false;
+ private NotificationDotsPreference mNotificationSettingsChangedListener;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -177,14 +176,16 @@
}
// Listen to system notification dot settings while this UI is active.
- mNotificationDotsObserver = newNotificationSettingsObserver(
- getActivity(), (NotificationDotsPreference) preference);
- mNotificationDotsObserver.register();
+ mSettingsCache = SettingsCache.INSTANCE.get(getActivity());
+ mNotificationSettingsChangedListener =
+ ((NotificationDotsPreference) preference);
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ (NotificationDotsPreference) mNotificationSettingsChangedListener);
// Also listen if notification permission changes
- mNotificationDotsObserver.getResolver().registerContentObserver(
- Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
- mNotificationDotsObserver);
- mNotificationDotsObserver.dispatchOnChange();
+ mSettingsCache.register(NOTIFICATION_ENABLED_LISTENERS,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_ENABLED_LISTENERS);
return true;
case ALLOW_ROTATION_PREFERENCE_KEY:
@@ -251,9 +252,11 @@
@Override
public void onDestroy() {
- if (mNotificationDotsObserver != null) {
- mNotificationDotsObserver.unregister();
- mNotificationDotsObserver = null;
+ if (mSettingsCache != null) {
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.unregister(NOTIFICATION_ENABLED_LISTENERS,
+ mNotificationSettingsChangedListener);
}
super.onDestroy();
}
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 601e117..7abb653 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -150,8 +150,8 @@
private void handleDeferredResume() {
if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) {
- onDeferredResumed();
addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+ onDeferredResumed();
mDeferredResumePending = false;
} else {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 3b7bcc2..a8a7e94 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import android.view.Display;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
@@ -182,20 +183,26 @@
}
/** Creates and up-to-date DisplayController.Info for the given context. */
+ @Nullable
public Info createInfoForContext(Context context) {
- Display display = Utilities.ATLEAST_R
- ? context.getDisplay()
- : context
- .getSystemService(DisplayManager.class)
- .getDisplay(mId);
- return display == null
- ? new Info(context)
- : new Info(context, display);
+ Display display = Utilities.ATLEAST_R ? context.getDisplay() : null;
+ if (display == null) {
+ display = context.getSystemService(DisplayManager.class).getDisplay(mId);
+ }
+ if (display == null) {
+ return null;
+ }
+ // Refresh the Context the prevent stale DisplayMetrics.
+ Context displayContext = context.getApplicationContext().createDisplayContext(display);
+ return new Info(displayContext, display);
}
protected void handleOnChange() {
Info oldInfo = mInfo;
Info newInfo = createInfoForContext(mDisplayContext);
+ if (newInfo == null) {
+ return;
+ }
int change = 0;
if (newInfo.hasDifferentSize(oldInfo)) {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7b26427..08ec591 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -92,6 +92,14 @@
}
/**
+ * Returns whether the target app is installed for a given user
+ */
+ public boolean isAppInstalled(String packageName, UserHandle user) {
+ ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+ return info != null;
+ }
+
+ /**
* Returns the application info for the provided package or null
*/
public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
@@ -105,7 +113,7 @@
}
public boolean isSafeMode() {
- return mContext.getPackageManager().isSafeMode();
+ return mPm.isSafeMode();
}
public Intent getAppLaunchIntent(String pkg, UserHandle user) {
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
deleted file mode 100644
index 9fe72ad..0000000
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-/**
- * Utility class to listen for secure settings changes
- */
-public class SecureSettingsObserver extends ContentObserver {
-
- /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
- public static final String NOTIFICATION_BADGING = "notification_badging";
- /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
- public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
- /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
- public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
- "swipe_bottom_to_notification_enabled";
-
- private final ContentResolver mResolver;
- private final String mKeySetting;
- private final int mDefaultValue;
- private final OnChangeListener mOnChangeListener;
-
- public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener,
- String keySetting, int defaultValue) {
- super(new Handler());
-
- mResolver = resolver;
- mOnChangeListener = listener;
- mKeySetting = keySetting;
- mDefaultValue = defaultValue;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mOnChangeListener.onSettingsChanged(getValue());
- }
-
- public boolean getValue() {
- return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1;
- }
-
- public void register() {
- mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this);
- }
-
- public ContentResolver getResolver() {
- return mResolver;
- }
-
- public void dispatchOnChange() {
- onChange(true);
- }
-
- public void unregister() {
- mResolver.unregisterContentObserver(this);
- }
-
- public interface OnChangeListener {
- void onSettingsChanged(boolean isEnabled);
- }
-
- public static SecureSettingsObserver newNotificationSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
- }
-
- public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener, ONE_HANDED_ENABLED, 0);
- }
-
- /**
- * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
- * preference.
- */
- public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener,
- ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
- }
-}
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
new file mode 100644
index 0000000..22b4d38
--- /dev/null
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * ContentObserver over Settings keys that also has a caching layer.
+ * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
+ * {@link #unregister(Uri, OnChangeListener)} methods.
+ *
+ * This can be used as a normal cache without any listeners as well via the
+ * {@link #getValue(Uri, int)} and {@link #dispatchOnChange(Uri)} to update (and subsequently call
+ * get)
+ *
+ * The cache will be invalidated/updated through the normal
+ * {@link ContentObserver#onChange(boolean)} calls
+ * or can be force updated by calling {@link #dispatchOnChange(Uri)}.
+ *
+ * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
+ */
+public class SettingsCache extends ContentObserver {
+
+ /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
+ public static final Uri NOTIFICATION_BADGING_URI =
+ Settings.Secure.getUriFor("notification_badging");
+ /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+ public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+ /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+ public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+ "swipe_bottom_to_notification_enabled";
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ public static final Uri NOTIFICATION_ENABLED_LISTENERS =
+ Settings.Secure.getUriFor("enabled_notification_listeners");
+ public static final Uri ROTATION_SETTING_URI =
+ Settings.System.getUriFor(ACCELEROMETER_ROTATION);
+
+ private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
+
+ /**
+ * Caches the last seen value for registered keys.
+ */
+ private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+ private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+ protected final ContentResolver mResolver;
+
+
+ /**
+ * Singleton instance
+ */
+ public static MainThreadInitializedObject<SettingsCache> INSTANCE =
+ new MainThreadInitializedObject<>(SettingsCache::new);
+
+ private SettingsCache(Context context) {
+ super(new Handler());
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ // We use default of 1, but if we're getting an onChange call, can assume a non-default
+ // value will exist
+ boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
+ if (!mListenerMap.containsKey(uri)) {
+ return;
+ }
+
+ for (OnChangeListener listener : mListenerMap.get(uri)) {
+ listener.onSettingsChanged(newVal);
+ }
+ }
+
+ /**
+ * Returns the value for this classes key from the cache. If not in cache, will call
+ * {@link #updateValue(Uri, int)} to fetch.
+ */
+ public boolean getValue(Uri keySetting, int defaultValue) {
+ if (mKeyCache.containsKey(keySetting)) {
+ return mKeyCache.get(keySetting);
+ } else {
+ return updateValue(keySetting, defaultValue);
+ }
+ }
+
+ /**
+ * Does not de-dupe if you add same listeners for the same key multiple times.
+ * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
+ */
+ public void register(Uri uri, OnChangeListener changeListener) {
+ if (mListenerMap.containsKey(uri)) {
+ mListenerMap.get(uri).add(changeListener);
+ } else {
+ CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
+ l.add(changeListener);
+ mListenerMap.put(uri, l);
+ mResolver.registerContentObserver(uri, false, this);
+ }
+ }
+
+ private boolean updateValue(Uri keyUri, int defaultValue) {
+ String key = keyUri.getLastPathSegment();
+ boolean newVal;
+ if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
+ newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
+ } else { // SETTING_SECURE
+ newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
+ }
+
+ mKeyCache.put(keyUri, newVal);
+ return newVal;
+ }
+
+ /**
+ * Force update a change for a given URI and have all listeners for that URI receive callbacks
+ * even if the value is unchanged.
+ */
+ public void dispatchOnChange(Uri uri) {
+ onChange(true, uri);
+ }
+
+ /**
+ * Call to stop receiving updates on the given {@param listener}.
+ * This Uri/Listener pair must correspond to the same pair called with for
+ * {@link #register(Uri, OnChangeListener)}
+ */
+ public void unregister(Uri uri, OnChangeListener listener) {
+ List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
+ if (!listenersToRemoveFrom.contains(listener)) {
+ return;
+ }
+
+ listenersToRemoveFrom.remove(listener);
+ if (listenersToRemoveFrom.isEmpty()) {
+ mListenerMap.remove(uri);
+ }
+ }
+
+ /**
+ * Don't use this. Ever.
+ * @param keyCache Cache to replace {@link #mKeyCache}
+ */
+ @VisibleForTesting
+ void setKeyCache(Map<Uri, Boolean> keyCache) {
+ mKeyCache = keyCache;
+ }
+
+ public interface OnChangeListener {
+ void onSettingsChanged(boolean isEnabled);
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index ae459e1..505c6ce 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -17,7 +17,7 @@
import android.content.Context;
import android.content.ContextWrapper;
-import android.view.ContextThemeWrapper;
+import android.graphics.Rect;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile;
@@ -49,6 +49,23 @@
return null;
}
+ default Rect getFolderBoundingBox() {
+ return getDeviceProfile().getAbsoluteOpenFolderBounds();
+ }
+
+ /**
+ * After calling {@link #getFolderBoundingBox()}, we calculate a (left, top) position for a
+ * Folder of size width x height to be within those bounds. However, the chosen position may
+ * not be visually ideal (e.g. uncanny valley of centeredness), so here's a chance to update it.
+ * @param inOutPosition A 2-size array where the first element is the left position of the open
+ * folder and the second element is the top position. Should be updated in place if desired.
+ * @param bounds The bounds that the open folder should fit inside.
+ * @param width The width of the open folder.
+ * @param height The height of the open folder.
+ */
+ default void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+ }
+
/**
* The root view to support drag-and-drop and popup support.
*/
@@ -56,10 +73,10 @@
DeviceProfile getDeviceProfile();
- static ActivityContext lookupContext(Context context) {
+ static <T extends ActivityContext> T lookupContext(Context context) {
if (context instanceof ActivityContext) {
- return (ActivityContext) context;
- } else if (context instanceof ContextThemeWrapper) {
+ return (T) context;
+ } else if (context instanceof ContextWrapper) {
return lookupContext(((ContextWrapper) context).getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find ActivityContext in parent tree");
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 15f7730..1939d15 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -206,15 +206,19 @@
protected boolean findActiveController(MotionEvent ev) {
mActiveController = null;
- 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
- // not active
+ if (canFindActiveController()) {
mActiveController = findControllerToHandleTouch(ev);
}
return mActiveController != null;
}
+ protected boolean canFindActiveController() {
+ // Only look for controllers if we are not dispatching from gesture area and proxy is
+ // not active
+ return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+ | TOUCH_DISPATCHING_FROM_PROXY)) == 0;
+ }
+
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// Shortcuts can appear above folder
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index addaf9c..0cb8c1e 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -118,7 +118,8 @@
mTargetRect.roundOut(outPos);
}
- public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
+ public static OptionsPopupView show(
+ Launcher launcher, RectF targetRect, List<OptionItem> items) {
OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
.inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
popup.mTargetRect = targetRect;
@@ -134,6 +135,7 @@
popup.mItemMap.put(view, item);
}
popup.show();
+ return popup;
}
@VisibleForTesting
diff --git a/tests/Android.mk b/tests/Android.mk
index 3d9077d..43d51fc 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -32,7 +32,6 @@
LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
../src/com/android/launcher3/ResourceUtils.java \
- ../src/com/android/launcher3/util/SecureSettingsObserver.java \
../src/com/android/launcher3/testing/TestProtocol.java
endif