Merge "Popup container color changes for Dark Theme Bug: 111411630" into ub-launcher3-qt-dev
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 734425e..39f8448 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -88,6 +88,10 @@
boolean gestureSwipeLeft) {
}
+ public void onSystemUiStateChanged(int stateFlags) {
+ // To be implemented
+ }
+
/** Deprecated methods **/
public void onQuickStep(MotionEvent motionEvent) { }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 225ae84..462e630 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -75,12 +75,4 @@
return new ScaleAndTranslation(scale, 0f, 0f);
}
- @Override
- public int getVisibleElements(Launcher launcher) {
- if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
- return super.getVisibleElements(launcher);
- }
- // Hide shelf content (e.g. QSB) because we fade it in when swiping up.
- return ALL_APPS_HEADER_EXTRA;
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index e903b6f..baef2eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -23,7 +23,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
@@ -44,11 +43,8 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -220,7 +216,7 @@
: mShelfState == ShelfAnimState.PEEK
? shelfPeekingProgress
: shelfOverviewProgress;
- mShelfAnim = createShelfProgressAnim(activity, toProgress);
+ mShelfAnim = createShelfAnim(activity, toProgress);
mShelfAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -238,10 +234,10 @@
LauncherState fromState, long transitionLength,
Consumer<AnimatorPlaybackController> callback) {
LauncherState endState = OVERVIEW;
- DeviceProfile dp = activity.getDeviceProfile();
- long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
if (wasVisible && fromState != BACKGROUND_APP) {
// If a translucent app was launched fom launcher, animate launcher states.
+ DeviceProfile dp = activity.getDeviceProfile();
+ long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
callback.accept(activity.getStateManager()
.createAnimationToNewWorkspace(fromState, endState, accuracy));
return;
@@ -254,11 +250,10 @@
if (!activity.getDeviceProfile().isVerticalBarLayout()
&& SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
// Don't animate the shelf when the mode is NO_BUTTON, because we update it atomically.
- Animator shiftAnim = createShelfProgressAnim(activity,
+ Animator shiftAnim = createShelfAnim(activity,
fromState.getVerticalProgress(activity),
endState.getVerticalProgress(activity));
anim.play(shiftAnim);
- anim.play(createShelfAlphaAnim(activity, endState, accuracy));
}
playScaleDownAnim(anim, activity, endState);
@@ -275,7 +270,7 @@
callback.accept(controller);
}
- private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) {
+ private Animator createShelfAnim(Launcher activity, float ... progressValues) {
Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
"allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
@@ -284,19 +279,6 @@
}
/**
- * Very quickly fade the alpha of shelf content.
- */
- private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) {
- AllAppsTransitionController allAppsController = activity.getAllAppsController();
- AnimatorSetBuilder animBuilder = new AnimatorSetBuilder();
- animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3);
- LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig();
- config.duration = accuracy;
- allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder);
- return animBuilder.build();
- }
-
- /**
* Scale down recents from the center task being full screen to being in overview.
*/
private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index 0924f38..ced9afa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -85,15 +85,24 @@
}
}
+ /** See {@link #finish(boolean, Runnable, boolean)} */
+ @UiThread
+ public void finish(boolean toRecents, Runnable onFinishComplete) {
+ finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
+ }
+
/**
* @param onFinishComplete A callback that runs on the main thread after the animation
* controller has finished on the background thread.
+ * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
+ * activity. If userLeaveHint is true, the activity will enter into
+ * picture-in-picture mode upon being paused.
*/
@UiThread
- public void finish(boolean toRecents, Runnable onFinishComplete) {
+ public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
Preconditions.assertUIThread();
if (!toRecents) {
- finishAndClear(false, onFinishComplete);
+ finishAndClear(false, onFinishComplete, sendUserLeaveHint);
} else {
if (mTouchInProgress) {
mFinishPending = true;
@@ -102,16 +111,17 @@
onFinishComplete.run();
}
} else {
- finishAndClear(true, onFinishComplete);
+ finishAndClear(true, onFinishComplete, sendUserLeaveHint);
}
}
}
- private void finishAndClear(boolean toRecents, Runnable onFinishComplete) {
+ private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+ boolean sendUserLeaveHint) {
SwipeAnimationTargetSet controller = targetSet;
targetSet = null;
if (controller != null) {
- controller.finishController(toRecents, onFinishComplete);
+ controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
}
}
@@ -163,7 +173,7 @@
mTouchInProgress = false;
if (mFinishPending) {
mFinishPending = false;
- finishAndClear(true /* toRecents */, null);
+ finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
}
if (mInputConsumer != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index 42a28fb..6e98a5a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -290,6 +290,10 @@
if (sysUiProxy == null) {
return null;
}
+ if (SysUINavigationMode.getMode(activity) == SysUINavigationMode.Mode.NO_BUTTON) {
+ // TODO(b/130225926): Temporarily disable pinning while gesture nav is enabled
+ return null;
+ }
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
return null;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 79f0c36..6044b61 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -20,6 +20,9 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
@@ -65,6 +68,8 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -135,6 +140,12 @@
@Override
public void onAssistantVisibilityChanged(float visibility) {
+ if (mOverviewComponentObserver == null) {
+ // Save the visibility to be applied when the user is unlocked
+ mPendingAssistantVisibility = visibility;
+ return;
+ }
+
MAIN_THREAD_EXECUTOR.execute(() -> {
mOverviewComponentObserver.getActivityControlHelper()
.onAssistantVisibilityChanged(visibility);
@@ -143,12 +154,20 @@
public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
+ if (mOverviewComponentObserver == null) {
+ return;
+ }
+
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
isButton, gestureSwipeLeft, activityControl.getContainerType());
}
+ public void onSystemUiStateChanged(int stateFlags) {
+ mSystemUiStateFlags = stateFlags;
+ }
+
/** Deprecated methods **/
public void onQuickStep(MotionEvent motionEvent) { }
@@ -185,6 +204,8 @@
private InputConsumerController mInputConsumer;
private SwipeSharedState mSwipeSharedState;
private boolean mAssistantAvailable;
+ private float mPendingAssistantVisibility = 0;
+ private @SystemUiStateFlags int mSystemUiStateFlags;
private boolean mIsUserUnlocked;
private List<Runnable> mOnUserUnlockedCallbacks;
@@ -337,6 +358,8 @@
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.INSTANCE.get(this);
mOverviewComponentObserver = new OverviewComponentObserver(this);
+ mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
+ mPendingAssistantVisibility);
mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
@@ -397,7 +420,8 @@
MotionEvent event = (MotionEvent) ev;
TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
if (event.getAction() == ACTION_DOWN) {
- if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
+ if (isInValidSystemUiState()
+ && mSwipeTouchRegion.contains(event.getX(), event.getY())) {
boolean useSharedState = mConsumer.isActive();
mConsumer.onConsumerAboutToBeSwitched();
mConsumer = newConsumer(useSharedState, event);
@@ -410,9 +434,15 @@
mUncheckedConsumer.onMotionEvent(event);
}
+ private boolean isInValidSystemUiState() {
+ return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
+ }
+
private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
// TODO: this makes a binder call every touch down. we should move to a listener pattern.
- if (mKM.isDeviceLocked()) {
+ if (!mIsUserUnlocked || mKM.isDeviceLocked()) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
// while device is locked even after exiting direct boot mode (e.g. camera).
return new DeviceLockedInputConsumer(this);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index a974135..5a1d387 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -1130,7 +1130,8 @@
private void finishCurrentTransitionToHome() {
synchronized (mRecentsAnimationWrapper) {
mRecentsAnimationWrapper.finish(true /* toRecents */,
- () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+ () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
+ true /* sendUserLeaveHint */);
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
doLogGesture(HOME);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index cbac944..1242d79 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -102,7 +102,7 @@
public ClipAnimationHelper(Context context) {
mWindowCornerRadius = getWindowCornerRadius(context.getResources());
mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
- mTaskCornerRadius = Themes.getDialogCornerRadius(context);
+ mTaskCornerRadius = TaskCornerRadius.get(context);
}
private void updateSourceStack(RemoteAnimationTargetCompat target) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index b682481..5a1a103 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -53,11 +53,11 @@
this.mOnFinishListener = onFinishListener;
}
- public void finishController(boolean toRecents, Runnable callback) {
+ public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
mOnFinishListener.accept(this);
BACKGROUND_EXECUTOR.execute(() -> {
controller.setInputConsumerEnabled(false);
- controller.finish(toRecents);
+ controller.finish(toRecents, sendUserLeaveHint);
if (callback != null) {
MAIN_THREAD_EXECUTOR.execute(callback);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java
new file mode 100644
index 0000000..3ddf1b6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+
+import android.content.Context;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class TaskCornerRadius {
+
+ public static float get(Context context) {
+ return supportsRoundedCornersOnWindows(context.getResources()) ?
+ Themes.getDialogCornerRadius(context):
+ context.getResources().getDimension(R.dimen.task_corner_radius_small);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index ddb94d2..d8aeb35 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -46,6 +46,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
@@ -322,6 +323,8 @@
mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
mEmptyMessagePaint.setTextSize(getResources()
.getDimension(R.dimen.recents_empty_message_text_size));
+ mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
+ Typeface.NORMAL));
mEmptyMessagePadding = getResources()
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 7905230..ed68d87 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -48,6 +48,7 @@
import com.android.launcher3.util.Themes;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
+import com.android.quickstep.util.TaskCornerRadius;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.QuickStepContract;
@@ -108,7 +109,7 @@
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mCornerRadius = Themes.getDialogCornerRadius(context);
+ mCornerRadius = TaskCornerRadius.get(context);
mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6ec3bf6..c5a1aca 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -19,6 +19,8 @@
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
+ <!-- For screens without rounded corners -->
+ <dimen name="task_corner_radius_small">2dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 482bbde..b263a4c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -42,7 +42,6 @@
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsModel;
@@ -87,6 +86,9 @@
}
OverviewInteractionState.INSTANCE.get(launcher)
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
+ if (launcher != null && launcher.getDragLayer() != null) {
+ launcher.getDragLayer().setDisallowBackGesture(shouldBackButtonBeHidden);
+ }
}
public static void onCreate(Launcher launcher) {
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index bfe31d1..47f4f4d 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -27,8 +27,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
import java.lang.annotation.Retention;
@@ -118,14 +116,10 @@
}
public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
- if (SysUINavigationMode.getMode(context) == Mode.NO_BUTTON) {
- // Track the bottom of the window rather than the top of the shelf.
- int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
- int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
- R.dimen.task_card_vert_space);
- return shelfHeight + spaceBetweenShelfAndRecents;
- }
- // Start from a third of bottom inset to provide some shelf overlap.
- return dp.hotseatBarSizePx + dp.getInsets().bottom / 3 - dp.edgeMarginPx * 2;
+ // Track the bottom of the window.
+ int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+ int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
+ R.dimen.task_card_vert_space);
+ return shelfHeight + spaceBetweenShelfAndRecents;
}
}
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 92f70e6..ca6f409 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -33,7 +33,6 @@
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
- android:fontFamily="sans-serif"
launcher:layoutHorizontal="true"
launcher:iconDisplay="shortcut_popup"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 04f3d02..4b7097a 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -31,7 +31,6 @@
android:paddingEnd="@dimen/popup_padding_end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
- android:fontFamily="sans-serif"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
android:focusable="false" />
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index c77b0b9..64f2362 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -34,7 +34,6 @@
android:layout_weight="1"
android:ellipsize="end"
android:fadingEdge="horizontal"
- android:fontFamily="sans-serif-condensed"
android:gravity="start"
android:singleLine="true"
android:maxLines="1"
@@ -51,7 +50,6 @@
android:layout_marginLeft="5dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
- android:fontFamily="sans-serif-condensed"
android:alpha="0.8" />
</LinearLayout>
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index 6bf9048..3fdfc96 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -26,7 +26,7 @@
android:theme="?attr/widgetsTheme">
<TextView
- style="@style/TextTitle"
+ style="@style/TextHeadline"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
index 33c981a..fc509d1 100644
--- a/res/layout/widgets_scroll_container.xml
+++ b/res/layout/widgets_scroll_container.xml
@@ -23,6 +23,7 @@
android:scrollbars="none">
<LinearLayout
android:id="@+id/widgets_cell_list"
+ style="@style/TextTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fac7793..9b84cc9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -126,7 +126,7 @@
<style name="WidgetContainerTheme.Dark" />
- <style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault">
+ <style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
<item name="android:layout_width">wrap_content</item>
<item name="android:minWidth">@dimen/fastscroll_popup_width</item>
<item name="android:layout_height">@dimen/fastscroll_popup_height</item>
@@ -173,7 +173,7 @@
</style>
<!-- Drop targets -->
- <style name="DropTargetButtonBase">
+ <style name="DropTargetButtonBase" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:drawablePadding">7.5dp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
@@ -189,6 +189,8 @@
<style name="DropTargetButton" parent="DropTargetButtonBase" />
+ <style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+
<style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
<style name="AllAppsEmptySearchBackground">
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 62bc53a..0d9bd31 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -50,6 +50,7 @@
import java.io.IOException;
import java.util.Locale;
+import java.util.function.Supplier;
/**
* Layout parsing code for auto installs layout
@@ -76,12 +77,8 @@
if (customizationApkInfo == null) {
return null;
}
- return get(context, customizationApkInfo.first, customizationApkInfo.second,
- appWidgetHost, callback);
- }
-
- static AutoInstallsLayout get(Context context, String pkg, Resources targetRes,
- AppWidgetHost appWidgetHost, LayoutParserCallback callback) {
+ String pkg = customizationApkInfo.first;
+ Resources targetRes = customizationApkInfo.second;
InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
// Try with grid size and hotseat count
@@ -114,7 +111,7 @@
// Object Tags
private static final String TAG_INCLUDE = "include";
- private static final String TAG_WORKSPACE = "workspace";
+ public static final String TAG_WORKSPACE = "workspace";
private static final String TAG_APP_ICON = "appicon";
private static final String TAG_AUTO_INSTALL = "autoinstall";
private static final String TAG_FOLDER = "folder";
@@ -156,7 +153,7 @@
protected final PackageManager mPackageManager;
protected final Resources mSourceRes;
- protected final int mLayoutId;
+ protected final Supplier<XmlPullParser> mInitialLayoutSupplier;
private final InvariantDeviceProfile mIdp;
private final int mRowCount;
@@ -171,6 +168,12 @@
public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback, Resources res,
int layoutId, String rootTag) {
+ this(context, appWidgetHost, callback, res, () -> res.getXml(layoutId), rootTag);
+ }
+
+ public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
+ LayoutParserCallback callback, Resources res,
+ Supplier<XmlPullParser> initialLayoutSupplier, String rootTag) {
mContext = context;
mAppWidgetHost = appWidgetHost;
mCallback = callback;
@@ -180,7 +183,7 @@
mRootTag = rootTag;
mSourceRes = res;
- mLayoutId = layoutId;
+ mInitialLayoutSupplier = initialLayoutSupplier;
mIdp = LauncherAppState.getIDP(context);
mRowCount = mIdp.numRows;
@@ -193,9 +196,9 @@
public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
mDb = db;
try {
- return parseLayout(mLayoutId, screenIds);
+ return parseLayout(mInitialLayoutSupplier.get(), screenIds);
} catch (Exception e) {
- Log.e(TAG, "Error parsing layout: " + e);
+ Log.e(TAG, "Error parsing layout: ", e);
return -1;
}
}
@@ -203,9 +206,8 @@
/**
* Parses the layout and returns the number of elements added on the homescreen.
*/
- protected int parseLayout(int layoutId, IntArray screenIds)
+ protected int parseLayout(XmlPullParser parser, IntArray screenIds)
throws XmlPullParserException, IOException {
- XmlPullParser parser = mSourceRes.getXml(layoutId);
beginDocument(parser, mRootTag);
final int depth = parser.getDepth();
int type;
@@ -248,7 +250,7 @@
final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
if (resId != 0) {
// recursively load some more favorites, why not?
- return parseLayout(resId, screenIds);
+ return parseLayout(mSourceRes.getXml(resId), screenIds);
} else {
return 0;
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 39d93c8..f830301 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -34,6 +34,7 @@
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -51,8 +52,10 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.BaseColumns;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Xml;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -63,15 +66,21 @@
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
+import org.xmlpull.v1.XmlPullParser;
+
import java.io.File;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
+import java.io.StringReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -93,8 +102,6 @@
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
- private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
-
private final ChangeListenerWrapper mListenerWrapper = new ChangeListenerWrapper();
private Handler mListenerHandler;
@@ -505,25 +512,40 @@
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
Context ctx = getContext();
- UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
- Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
- if (bundle == null) {
+ InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
+
+ String authority = Settings.Secure.getString(ctx.getContentResolver(),
+ "launcher3.layout.provider");
+ if (TextUtils.isEmpty(authority)) {
return null;
}
- String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME);
- if (packageName != null) {
- try {
- Resources targetResources = ctx.getPackageManager()
- .getResourcesForApplication(packageName);
- return AutoInstallsLayout.get(ctx, packageName, targetResources,
- widgetHost, mOpenHelper);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Target package for restricted profile not found", e);
- return null;
- }
+ ProviderInfo pi = ctx.getPackageManager().resolveContentProvider(authority, 0);
+ if (pi == null) {
+ Log.e(TAG, "No provider found for authority " + authority);
+ return null;
}
- return null;
+ Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
+ .appendQueryParameter("version", "1")
+ .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
+ .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
+ .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
+ .build();
+
+ try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {
+ // Read the full xml so that we fail early in case of any IO error.
+ String layout = new String(IOUtils.toByteArray(in));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new StringReader(layout));
+
+ Log.d(TAG, "Loading layout from " + authority);
+ return new AutoInstallsLayout(ctx, widgetHost, mOpenHelper,
+ ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo),
+ () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
+ } catch (Exception e) {
+ Log.e(TAG, "Error getting layout stream from: " + authority , e);
+ return null;
+ }
}
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9f902ed..6cc49de 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -24,10 +24,12 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -43,6 +45,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
@@ -54,6 +57,8 @@
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* A ViewGroup that coordinates dragging across its descendants
@@ -68,6 +73,9 @@
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
+ private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
+ Collections.singletonList(new Rect());
+
@Thunk DragController mDragController;
// Variables relating to animation of views after drop
@@ -86,6 +94,8 @@
private final ViewGroupFocusHelper mFocusIndicatorHelper;
private final WorkspaceAndHotseatScrim mScrim;
+ private boolean mDisallowBackGesture;
+
/**
* Used to create a new DragLayer from XML.
*
@@ -552,6 +562,24 @@
mScrim.onInsetsChanged(insets);
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b);
+ setDisallowBackGesture(mDisallowBackGesture);
+ }
+
+ @TargetApi(Build.VERSION_CODES.Q)
+ public void setDisallowBackGesture(boolean disallowBackGesture) {
+ if (!Utilities.ATLEAST_Q) {
+ return;
+ }
+ mDisallowBackGesture = disallowBackGesture;
+ setSystemGestureExclusionRects(mDisallowBackGesture
+ ? SYSTEM_GESTURE_EXCLUSION_RECT
+ : Collections.emptyList());
+ }
+
public WorkspaceAndHotseatScrim getScrim() {
return mScrim;
}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 59fd859..a45f17d 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -32,6 +32,14 @@
*/
public class Themes {
+ public static String getDefaultBodyFont(Context context) {
+ TypedArray ta = context.obtainStyledAttributes(android.R.style.TextAppearance_DeviceDefault,
+ new int[]{android.R.attr.fontFamily});
+ String value = ta.getString(0);
+ ta.recycle();
+ return value;
+ }
+
public static float getDialogCornerRadius(Context context) {
return getDimension(context, android.R.attr.dialogCornerRadius,
context.getResources().getDimension(R.dimen.default_dialog_corner_radius));
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 5889468..fab21fa 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -34,6 +34,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
@@ -73,6 +74,7 @@
private static final Rect sTmpRect = new Rect();
private Runnable mEndRunnable;
+ private CancellationSignal mLoadIconSignal;
private final int mBlurSizeOutline;
@@ -153,6 +155,9 @@
@Override
public void onAnimationEnd(Animator animator) {
+ if (mLoadIconSignal != null) {
+ mLoadIconSignal.cancel();
+ }
if (mEndRunnable != null) {
mEndRunnable.run();
} else {
@@ -186,7 +191,7 @@
@WorkerThread
private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
- Runnable onIconLoadedRunnable) {
+ Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
final LayoutParams lp = (LayoutParams) getLayoutParams();
Drawable drawable = null;
boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
@@ -290,7 +295,9 @@
setBackground(finalDrawable);
}
- onIconLoadedRunnable.run();
+ if (!loadIconSignal.isCanceled()) {
+ onIconLoadedRunnable.run();
+ }
invalidate();
invalidateOutline();
});
@@ -386,6 +393,7 @@
// Get the drawable on the background thread
// Must be called after matchPositionOf so that we know what size to load.
if (originalView.getTag() instanceof ItemInfo) {
+ view.mLoadIconSignal = new CancellationSignal();
Runnable onIconLoaded = () -> {
// Delay swapping views until the icon is loaded to prevent a flash.
view.setVisibility(VISIBLE);
@@ -393,9 +401,10 @@
originalView.setVisibility(INVISIBLE);
}
};
+ CancellationSignal loadIconSignal = view.mLoadIconSignal;
new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(), isOpening,
- onIconLoaded);
+ onIconLoaded, loadIconSignal);
});
}
@@ -461,6 +470,10 @@
setScaleY(1);
setAlpha(1);
setBackground(null);
+ if (mLoadIconSignal != null) {
+ mLoadIconSignal.cancel();
+ }
+ mLoadIconSignal = null;
mEndRunnable = null;
mIsAdaptiveIcon = false;
mForeground = null;
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 883cbee..5653801 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -24,6 +24,7 @@
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Property;
import android.view.MotionEvent;
@@ -31,13 +32,16 @@
import android.view.ViewConfiguration;
import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.FastScrollThumbDrawable;
import com.android.launcher3.util.Themes;
-import androidx.recyclerview.widget.RecyclerView;
+import java.util.Collections;
+import java.util.List;
/**
* The track and scrollbar that shows when you scroll the list.
@@ -65,6 +69,9 @@
private final static int SCROLL_BAR_VIS_DURATION = 150;
private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
+ private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
+ Collections.singletonList(new Rect());
+
private final int mMinWidth;
private final int mMaxWidth;
private final int mThumbPadding;
@@ -81,6 +88,8 @@
private final Paint mThumbPaint;
protected final int mThumbHeight;
+ private final RectF mThumbBounds = new RectF();
+ private final Point mThumbDrawOffset = new Point();
private final Paint mTrackPaint;
@@ -292,15 +301,23 @@
}
int saveCount = canvas.save();
canvas.translate(getWidth() / 2, mRv.getScrollBarTop());
+ mThumbDrawOffset.set(getWidth() / 2, mRv.getScrollBarTop());
// Draw the track
float halfW = mWidth / 2;
canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
mWidth, mWidth, mTrackPaint);
canvas.translate(0, mThumbOffsetY);
+ mThumbDrawOffset.y += mThumbOffsetY;
halfW += mThumbPadding;
float r = getScrollThumbRadius();
- canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint);
+ mThumbBounds.set(-halfW, 0, halfW, mThumbHeight);
+ canvas.drawRoundRect(mThumbBounds, r, r, mThumbPaint);
+ if (Utilities.ATLEAST_Q) {
+ mThumbBounds.roundOut(SYSTEM_GESTURE_EXCLUSION_RECT.get(0));
+ SYSTEM_GESTURE_EXCLUSION_RECT.get(0).offset(mThumbDrawOffset.x, mThumbDrawOffset.y);
+ setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
+ }
canvas.restoreToCount(saveCount);
}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index 0edb3d6..fa23b8d 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DONT_KILL_APP;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import android.app.Activity;
import android.app.ActivityManager;
@@ -28,6 +29,13 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Base64;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import androidx.test.InstrumentationRegistry;
@@ -104,4 +112,19 @@
Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
return inst.getTargetContext().getContentResolver().call(uri, command, arg, null);
}
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ String path = Base64.encodeToString(uri.getPath().getBytes(),
+ Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
+ File file = new File(getContext().getCacheDir(), path);
+ if (!file.exists()) {
+ // Create an empty file so that we can pass its descriptor
+ try {
+ file.createNewFile();
+ } catch (IOException e) { }
+ }
+
+ return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
new file mode 100644
index 0000000..1efdee8
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStreamWriter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiSelector;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultLayoutProviderTest extends AbstractLauncherUiTest {
+
+ @Rule
+ public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+
+ private static final String SETTINGS_APP = "com.android.settings";
+
+ private Context mContext;
+ private String mAuthority;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mContext = InstrumentationRegistry.getContext();
+
+ PackageManager pm = mTargetContext.getPackageManager();
+ ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext,
+ TestCommandReceiver.class), 0);
+ mAuthority = pi.authority;
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
+ writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
+
+ // Launch the home activity
+ mActivityMonitor.startLauncher();
+ waitForModelLoaded();
+
+ // Verify widget present
+ UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
+ .description(getSettingsApp().getLabel().toString());
+ assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_widget() throws Exception {
+ // A non-restored widget with no config screen gets restored automatically.
+ LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
+
+ writeLayout(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
+ .putWidget(info.getComponent().getPackageName(),
+ info.getComponent().getClassName(), 2, 2));
+
+ // Launch the home activity
+ mActivityMonitor.startLauncher();
+ waitForModelLoaded();
+
+ // Verify widget present
+ UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
+ .className(LauncherAppWidgetHostView.class).description(info.label);
+ assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_folder() throws Exception {
+ writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
+ .addApp(SETTINGS_APP, SETTINGS_APP)
+ .addApp(SETTINGS_APP, SETTINGS_APP)
+ .addApp(SETTINGS_APP, SETTINGS_APP)
+ .build());
+
+ // Launch the home activity
+ mActivityMonitor.startLauncher();
+ waitForModelLoaded();
+
+ // Verify widget present
+ UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
+ .descriptionContains(mTargetContext.getString(android.R.string.copy));
+ assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+ }
+
+ @After
+ public void cleanup() throws Exception {
+ mDevice.executeShellCommand("settings delete secure launcher3.layout.provider");
+ }
+
+ private void writeLayout(LauncherLayoutBuilder builder) throws Exception {
+ mDevice.executeShellCommand("settings put secure launcher3.layout.provider " + mAuthority);
+ ParcelFileDescriptor pfd = mTargetContext.getContentResolver().openFileDescriptor(
+ Uri.parse("content://" + mAuthority + "/launcher_layout"), "w");
+
+ try (OutputStreamWriter writer = new OutputStreamWriter(new AutoCloseOutputStream(pfd))) {
+ builder.build(writer);
+ }
+ clearLauncherData();
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
new file mode 100644
index 0000000..d3659eb
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util;
+
+
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class to build xml for Launcher Layout
+ */
+public class LauncherLayoutBuilder {
+
+ // Object Tags
+ private static final String TAG_WORKSPACE = "workspace";
+ private static final String TAG_AUTO_INSTALL = "autoinstall";
+ private static final String TAG_FOLDER = "folder";
+ private static final String TAG_APPWIDGET = "appwidget";
+ private static final String TAG_EXTRA = "extra";
+
+ private static final String ATTR_CONTAINER = "container";
+ private static final String ATTR_RANK = "rank";
+
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_CLASS_NAME = "className";
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_SCREEN = "screen";
+
+ // x and y can be specified as negative integers, in which case -1 represents the
+ // last row / column, -2 represents the second last, and so on.
+ private static final String ATTR_X = "x";
+ private static final String ATTR_Y = "y";
+ private static final String ATTR_SPAN_X = "spanX";
+ private static final String ATTR_SPAN_Y = "spanY";
+
+ private static final String ATTR_CHILDREN = "children";
+
+
+ // Style attrs -- "Extra"
+ private static final String ATTR_KEY = "key";
+ private static final String ATTR_VALUE = "value";
+
+ private static final String CONTAINER_DESKTOP = "desktop";
+ private static final String CONTAINER_HOTSEAT = "hotseat";
+
+ private final ArrayList<Pair<String, HashMap<String, Object>>> mNodes = new ArrayList<>();
+
+ public Location atHotseat(int rank) {
+ Location l = new Location();
+ l.items.put(ATTR_CONTAINER, CONTAINER_HOTSEAT);
+ l.items.put(ATTR_RANK, Integer.toString(rank));
+ return l;
+ }
+
+ public Location atWorkspace(int x, int y, int screen) {
+ Location l = new Location();
+ l.items.put(ATTR_CONTAINER, CONTAINER_DESKTOP);
+ l.items.put(ATTR_X, Integer.toString(x));
+ l.items.put(ATTR_Y, Integer.toString(y));
+ l.items.put(ATTR_SCREEN, Integer.toString(screen));
+ return l;
+ }
+
+ public String build() throws IOException {
+ StringWriter writer = new StringWriter();
+ build(writer);
+ return writer.toString();
+ }
+
+ public void build(Writer writer) throws IOException {
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(writer);
+
+ serializer.startDocument("UTF-8", true);
+ serializer.startTag(null, TAG_WORKSPACE);
+ writeNodes(serializer, mNodes);
+ serializer.endTag(null, TAG_WORKSPACE);
+ serializer.endDocument();
+ serializer.flush();
+ }
+
+ private static void writeNodes(XmlSerializer serializer,
+ ArrayList<Pair<String, HashMap<String, Object>>> nodes) throws IOException {
+ for (Pair<String, HashMap<String, Object>> node : nodes) {
+ ArrayList<Pair<String, HashMap<String, Object>>> children = null;
+
+ serializer.startTag(null, node.first);
+ for (Map.Entry<String, Object> attr : node.second.entrySet()) {
+ if (ATTR_CHILDREN.equals(attr.getKey())) {
+ children = (ArrayList<Pair<String, HashMap<String, Object>>>) attr.getValue();
+ } else {
+ serializer.attribute(null, attr.getKey(), (String) attr.getValue());
+ }
+ }
+
+ if (children != null) {
+ writeNodes(serializer, children);
+ }
+ serializer.endTag(null, node.first);
+ }
+ }
+
+ public class Location {
+
+ final HashMap<String, Object> items = new HashMap<>();
+
+ public LauncherLayoutBuilder putApp(String packageName, String className) {
+ items.put(ATTR_PACKAGE_NAME, packageName);
+ items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className);
+ mNodes.add(Pair.create(TAG_AUTO_INSTALL, items));
+ return LauncherLayoutBuilder.this;
+ }
+
+ public LauncherLayoutBuilder putWidget(String packageName, String className,
+ int spanX, int spanY) {
+ items.put(ATTR_PACKAGE_NAME, packageName);
+ items.put(ATTR_CLASS_NAME, className);
+ items.put(ATTR_SPAN_X, Integer.toString(spanX));
+ items.put(ATTR_SPAN_Y, Integer.toString(spanY));
+ mNodes.add(Pair.create(TAG_APPWIDGET, items));
+ return LauncherLayoutBuilder.this;
+ }
+
+ public FolderBuilder putFolder(int titleResId) {
+ FolderBuilder folderBuilder = new FolderBuilder();
+ items.put(ATTR_TITLE, Integer.toString(titleResId));
+ items.put(ATTR_CHILDREN, folderBuilder.mChildren);
+ mNodes.add(Pair.create(TAG_FOLDER, items));
+ return folderBuilder;
+ }
+ }
+
+ public class FolderBuilder {
+
+ final ArrayList<Pair<String, HashMap<String, Object>>> mChildren = new ArrayList<>();
+
+ public FolderBuilder addApp(String packageName, String className) {
+ HashMap<String, Object> items = new HashMap<>();
+ items.put(ATTR_PACKAGE_NAME, packageName);
+ items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className);
+ mChildren.add(Pair.create(TAG_AUTO_INSTALL, items));
+ return this;
+ }
+
+ public LauncherLayoutBuilder build() {
+ return LauncherLayoutBuilder.this;
+ }
+ }
+}