Revert "Merge branch 'ub-launcher3-master' into pi-dev"
Original CL loses the commit history. I believe this is due to doing the merge on master and cherry picking to pi-dev. Tested locally that reverting this results in no conflicts when doing the merge properly on pi-dev.
This reverts commit 3f7df53dda43d05c7a40ed8c4114b63189936ffe.
Bug: 74794600
Test: manual test
Change-Id: I58f3bb1bd5ce789be380bac9716efd2627a90f92
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c24850d..bb03f50 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -87,11 +87,9 @@
android:process=":wallpaper_chooser"
android:permission="android.permission.BIND_JOB_SERVICE" />
- <service
- android:name="com.android.launcher3.notification.NotificationListener"
- android:label="@string/icon_badging_service_title"
- android:enabled="@bool/notification_badging_enabled"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <service android:name="com.android.launcher3.notification.NotificationListener"
+ android:enabled="@bool/notification_badging_enabled"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c3ddfd6..6ef7828 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -71,7 +71,7 @@
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
- android:screenOrientation="unspecified"
+ android:screenOrientation="nosensor"
android:configChanges="keyboard|keyboardHidden|navigation"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
diff --git a/proguard.flags b/proguard.flags
index cac8930..b8cade5 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -102,11 +102,6 @@
public <init>(...);
}
-# InstantAppResolver
--keep class com.android.quickstep.InstantAppResolverImpl {
- public <init>(...);
-}
-
-keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
*;
}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d531a46..02b4379 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -36,13 +36,8 @@
android:restoreAnyVersion="true"
android:supportsRtl="true" >
- <service
- android:name="com.android.quickstep.TouchInteractionService"
- android:permission="android.permission.STATUS_BAR_SERVICE" >
- <intent-filter>
- <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
- </intent-filter>
- </service>
+ <service android:name="com.android.quickstep.TouchInteractionService"
+ android:exported="true" />
<!-- STOPSHIP: Change exported to false once all the integration is complete.
It is set to true so that the activity can be started from command line -->
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 9006831..d5859a7 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
deleted file mode 100644
index c416844..0000000
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.quickstep.RecentsRootView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <com.android.quickstep.FallbackRecentsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:theme="@style/HomeScreenElementTheme"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false" />
-
-</com.android.quickstep.RecentsRootView>
\ No newline at end of file
diff --git a/res/layout/longpress_options_menu.xml b/quickstep/res/layout/longpress_options_menu.xml
similarity index 96%
rename from res/layout/longpress_options_menu.xml
rename to quickstep/res/layout/longpress_options_menu.xml
index 71d117a..9cf0fcf 100644
--- a/res/layout/longpress_options_menu.xml
+++ b/quickstep/res/layout/longpress_options_menu.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.views.OptionsPopupView
+<com.android.launcher3.uioverrides.OptionsPopupView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -94,4 +94,4 @@
</FrameLayout>
-</com.android.launcher3.views.OptionsPopupView>
\ No newline at end of file
+</com.android.launcher3.uioverrides.OptionsPopupView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 54a90cf..9f4f8a1 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.LauncherRecentsView
+<com.android.quickstep.RecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
@@ -24,4 +24,8 @@
android:alpha="0.0"
android:visibility="invisible" >
-</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
+ <com.android.launcher3.uioverrides.WorkspaceCard
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</com.android.quickstep.RecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 0ac2b11..91b6aa3 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,12 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp">
- <com.android.quickstep.views.TaskThumbnailView
+ <com.android.quickstep.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -29,4 +29,4 @@
android:layout_width="@dimen/task_thumbnail_icon_size"
android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal" />
-</com.android.quickstep.views.TaskView>
\ No newline at end of file
+</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index b846665..6e3fb4f 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.TaskMenuView
+<com.android.quickstep.TaskMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
@@ -33,4 +33,4 @@
android:paddingTop="18dp"
android:drawablePadding="8dp"
android:gravity="center_horizontal"/>
-</com.android.quickstep.views.TaskMenuView>
\ No newline at end of file
+</com.android.quickstep.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 8497191..0956048 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,21 +16,24 @@
<resources>
+ <dimen name="options_menu_icon_size">24dp</dimen>
+
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<dimen name="task_menu_background_radius">12dp</dimen>
<dimen name="task_corner_radius">2dp</dimen>
<dimen name="task_fade_length">20dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
- <dimen name="recents_page_fade_length">100dp</dimen>
- <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
- loading full resolution screenshots. -->
- <dimen name="recents_fast_fling_velocity">600dp</dimen>
<dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
<dimen name="quickstep_fling_min_velocity">250dp</dimen>
+ <dimen name="workspace_overview_offset_x">-24dp</dimen>
+
+ <!-- TODO: This can be calculated using other resource values -->
+ <dimen name="all_apps_search_box_full_height">90dp</dimen>
+
<!-- Launcher app transition -->
<dimen name="content_trans_y">25dp</dimen>
<dimen name="workspace_trans_y">80dp</dimen>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 2bd9f8f..ba99d81 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -16,7 +16,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
-
- <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index ba0cbfa..0fe29e3 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,8 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
@@ -32,13 +31,11 @@
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
@@ -50,18 +47,14 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.DrawableFactory;
-import com.android.launcher3.shortcuts.DeepShortcutTextView;
-import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.quickstep.RecentsAnimationInterpolator;
import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -81,16 +74,12 @@
private static final String TAG = "LauncherTransition";
private static final int REFRESH_RATE_MS = 16;
- private static final int STATUS_BAR_TRANSITION_DURATION = 120;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
- private static final int APP_LAUNCH_DURATION = 500;
- // Use a shorter duration for x or y translation to create a curve effect
- private static final int APP_LAUNCH_CURVED_DURATION = 233;
private static final int RECENTS_LAUNCH_DURATION = 336;
- private static final int LAUNCHER_RESUME_START_DELAY = 100;
+ private static final int LAUNCHER_RESUME_START_DELAY = 150;
private static final int CLOSING_TRANSITION_DURATION_MS = 350;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -162,7 +151,6 @@
@Override
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
- TaskView taskView = findTaskViewToLaunch(launcher, v);
try {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
@Override
@@ -172,8 +160,8 @@
// processed before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
final boolean removeTrackingView;
- LauncherTransitionAnimator animator = composeRecentsLaunchAnimator(
- taskView == null ? v : taskView, targets);
+ LauncherTransitionAnimator animator =
+ composeRecentsLaunchAnimator(v, targets);
if (animator != null) {
// We are animating the task view directly, do not remove it after
removeTrackingView = false;
@@ -213,10 +201,8 @@
}
};
- int duration = taskView != null ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
- int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
- runner, duration, statusBarTransitionDelay));
+ return ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(runner, 500, 380));
} catch (NoClassDefFoundError e) {
// Gracefully fall back to default launch options if the user's platform doesn't
// have the latest changes.
@@ -226,65 +212,19 @@
}
/**
- * Try to find a TaskView that corresponds with the component of the launched view.
- *
- * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
- * Otherwise, we will assume we are using a normal app transition, but it's possible that the
- * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
- */
- private TaskView findTaskViewToLaunch(Launcher launcher, View v) {
- if (v instanceof TaskView) {
- return (TaskView) v;
- }
- if (!launcher.isInState(LauncherState.OVERVIEW)) {
- return null;
- }
- if (v.getTag() instanceof ItemInfo) {
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- ComponentName componentName = itemInfo.getTargetComponent();
- if (componentName != null) {
- RecentsView recentsView = launcher.getOverviewPanel();
- for (int i = 0; i < recentsView.getChildCount(); i++) {
- TaskView taskView = (TaskView) recentsView.getPageAt(i);
- if (recentsView.isTaskViewVisible(taskView)) {
- Task task = taskView.getTask();
- if (componentName.equals(task.key.getComponent())) {
- return taskView;
- }
- }
- }
- }
- }
- return null;
- }
-
- /**
* Composes the animations for a launch from the recents list if possible.
*/
private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
RemoteAnimationTargetCompat[] targets) {
- RecentsView recentsView = mLauncher.getOverviewPanel();
- boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
- MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
- if (v instanceof TaskView) {
- // We already found a task view to launch, so use that for the animation.
- TaskView taskView = (TaskView) v;
- return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
- getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
- }
-
- // It's possible that the launched view can still be resolved to a visible task view, check
- // the task id of the opening task and see if we can find a match.
-
// Ensure recents is actually visible
- if (!mLauncher.getStateManager().getState().overviewUi) {
+ if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
return null;
}
// Resolve the opening task id
int openingTaskId = -1;
for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == MODE_OPENING) {
+ if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
openingTaskId = target.taskId;
break;
}
@@ -297,40 +237,15 @@
// If the opening task id is not currently visible in overview, then fall back to normal app
// icon launch animation
+ RecentsView recentsView = mLauncher.getOverviewPanel();
TaskView taskView = recentsView.getTaskView(openingTaskId);
if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
return null;
}
// Found a visible recents task that matches the opening app, lets launch the app from there
- Animator launcherAnim;
- AnimatorListenerAdapter windowAnimEndListener;
- if (launcherClosing) {
- launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
- windowAnimEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Make sure recents gets fixed up by resetting task alphas and scales, etc.
- mLauncher.getStateManager().reapplyState();
- }
- };
- } else {
- AnimatorPlaybackController controller =
- mLauncher.getStateManager()
- .createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
- controller.dispatchOnStart();
- launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
- windowAnimEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLauncher.getStateManager().goToState(NORMAL, false);
- }
- };
- }
-
- Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
- windowAnim.addListener(windowAnimEndListener);
- return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
+ return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
+ getRecentsWindowAnimator(taskView, targets));
}
/**
@@ -345,16 +260,15 @@
int launchedTaskIndex = recentsView.indexOfChild(v);
int centerTaskIndex = recentsView.getCurrentPage();
boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
- boolean isRtl = recentsView.isRtl();
if (launchingCenterTask) {
- if (launchedTaskIndex - 1 >= 0) {
+ if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
ObjectAnimator adjacentTask1ScaleAndTranslate =
LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
new PropertyListBuilder()
.scale(adjacentPage1.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
- .translationX(isRtl ? mRecentsTransX : -mRecentsTransX)
+ .translationX(mIsRtl ? mRecentsTransX : -mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask1ScaleAndTranslate);
}
@@ -365,11 +279,11 @@
new PropertyListBuilder()
.scale(adjacentTask2.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
- .translationX(isRtl ? -mRecentsTransX : mRecentsTransX)
+ .translationX(mIsRtl ? -mRecentsTransX : mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask2ScaleAndTranslate);
}
- } else {
+ } else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
// We are launching an adjacent task, so parallax the center and other adjacent task.
TaskView centerTask = (TaskView) recentsView.getPageAt(centerTaskIndex);
float translationX = Math.abs(v.getTranslationX());
@@ -377,11 +291,11 @@
LauncherAnimUtils.ofPropertyValuesHolder(centerTask,
new PropertyListBuilder()
.scale(v.getScaleX())
- .translationX(isRtl ? -translationX : translationX)
+ .translationX(mIsRtl ? -translationX : translationX)
.build());
launcherAnimator.play(centerTaskParallaxToRight);
int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
- if (otherAdjacentTaskIndex >= 0
+ if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
&& otherAdjacentTaskIndex < recentsView.getPageCount()) {
TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
otherAdjacentTaskIndex);
@@ -389,7 +303,7 @@
LauncherAnimUtils.ofPropertyValuesHolder(otherAdjacentTask,
new PropertyListBuilder()
.translationX(otherAdjacentTask.getTranslationX()
- + (isRtl ? -translationX : translationX))
+ + (mIsRtl ? -translationX : translationX))
.build());
launcherAnimator.play(otherAdjacentTaskParallaxToRight);
}
@@ -400,7 +314,7 @@
launcherAnimator.play(allAppsSlideOut);
Workspace workspace = mLauncher.getWorkspace();
- float[] workspaceScaleAndTranslation = NORMAL
+ float[] workspaceScaleAndTranslation = LauncherState.NORMAL
.getWorkspaceScaleAndTranslation(mLauncher);
Animator recenterWorkspace = LauncherAnimUtils.ofPropertyValuesHolder(
workspace, new PropertyListBuilder()
@@ -410,6 +324,9 @@
launcherAnimator.play(recenterWorkspace);
CellLayout currentWorkspacePage = (CellLayout) workspace.getPageAt(
workspace.getCurrentPage());
+ Animator hideWorkspaceScrim = ObjectAnimator.ofInt(
+ currentWorkspacePage.getScrimBackground(), DRAWABLE_ALPHA, 0);
+ launcherAnimator.play(hideWorkspaceScrim);
launcherAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnimator.setDuration(RECENTS_LAUNCH_DURATION);
@@ -420,7 +337,7 @@
* @return Animator that controls the window of the opening targets for the recents launch
* animation.
*/
- private ValueAnimator getRecentsWindowAnimator(TaskView v, MutableBoolean skipLauncherChanges,
+ private ValueAnimator getRecentsWindowAnimator(TaskView v,
RemoteAnimationTargetCompat[] targets) {
Rect taskViewBounds = new Rect();
mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -456,15 +373,13 @@
final float percent = animation.getAnimatedFraction();
TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
- if (!skipLauncherChanges.value) {
- v.setScaleX(tw.taskScale);
- v.setScaleY(tw.taskScale);
- v.setTranslationX(tw.taskX);
- v.setTranslationY(tw.taskY);
- // Defer fading out the view until after the app window gets faded in
- v.setAlpha(getValue(1f, 0f, 75, 75,
- appAnimator.getDuration() * percent, Interpolators.LINEAR));
- }
+ v.setScaleX(tw.taskScale);
+ v.setScaleY(tw.taskScale);
+ v.setTranslationX(tw.taskX);
+ v.setTranslationY(tw.taskY);
+ // Defer fading out the view until after the app window gets faded in
+ v.setAlpha(getValue(1f, 0f, 75, 75,
+ appAnimator.getDuration() * percent, Interpolators.LINEAR));
matrix.setScale(tw.winScale, tw.winScale);
matrix.postTranslate(tw.winX, tw.winY);
@@ -486,10 +401,7 @@
matrix.postTranslate(target.position.x, target.position.y);
t.setMatrix(target.leash, matrix);
t.setWindowCrop(target.leash, crop);
-
- if (!skipLauncherChanges.value) {
- t.deferTransactionUntil(target.leash, surface, frameNumber);
- }
+ t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
}
if (isFirstFrame) {
t.show(target.leash);
@@ -501,6 +413,13 @@
isFirstFrame = false;
}
});
+ appAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+ mLauncher.getStateManager().reapplyState();
+ }
+ });
return appAnimator;
}
@@ -578,40 +497,26 @@
* @return Animator that controls the icon used to launch the target.
*/
private AnimatorSet getIconAnimator(View v) {
- final boolean isBubbleTextView = v instanceof BubbleTextView;
+ boolean isBubbleTextView = v instanceof BubbleTextView;
mFloatingView = new View(mLauncher);
if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
// Create a copy of the app icon
- mFloatingView.setBackground(
- DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag()));
+ ItemInfoWithIcon info = (ItemInfoWithIcon) v.getTag();
+ FastBitmapDrawable d = DrawableFactory.get(mLauncher).newIcon(info);
+ d.setIsDisabled(info.isDisabled());
+ mFloatingView.setBackground(d);
}
// Position the floating view exactly on top of the original
Rect rect = new Rect();
- final boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
- && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
- if (isDeepShortcutTextView) {
- // Deep shortcut views have their icon drawn in a sibling view.
- DeepShortcutView view = (DeepShortcutView) v.getParent();
- mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
- } else {
- mDragLayer.getDescendantRectRelativeToSelf(v, rect);
- }
- final int viewLocationStart = mIsRtl
+ mDragLayer.getDescendantRectRelativeToSelf(v, rect);
+ int viewLocationStart = mIsRtl
? mDeviceProfile.widthPx - rect.right
: rect.left;
- final int viewLocationTop = rect.top;
+ int viewLocationTop = rect.top;
- float startScale = 1f;
- if (isBubbleTextView && !isDeepShortcutTextView) {
- BubbleTextView btv = (BubbleTextView) v;
- btv.getIconBounds(rect);
- Drawable dr = btv.getIcon();
- if (dr instanceof FastBitmapDrawable) {
- startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
- }
- } else {
- rect.set(0, 0, rect.width(), rect.height());
+ if (isBubbleTextView) {
+ ((BubbleTextView) v).getIconBounds(rect);
}
LayoutParams lp = new LayoutParams(rect.width(), rect.height());
lp.ignoreInsets = true;
@@ -639,8 +544,8 @@
// Adjust the duration to change the "curve" of the app icon to the center.
boolean isBelowCenterY = lp.topMargin < centerY;
- x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
- y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
+ x.setDuration(isBelowCenterY ? 500 : 233);
+ y.setDuration(isBelowCenterY ? 233 : 500);
x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
appIconAnimatorSet.play(x);
@@ -651,10 +556,14 @@
float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
float scale = Math.max(maxScaleX, maxScaleY);
- ObjectAnimator scaleAnim = ObjectAnimator
- .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
- scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
- appIconAnimatorSet.play(scaleAnim);
+ ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
+ ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
+ sX.setDuration(500);
+ sY.setDuration(500);
+ sX.setInterpolator(Interpolators.EXAGGERATED_EASE);
+ sY.setInterpolator(Interpolators.EXAGGERATED_EASE);
+ appIconAnimatorSet.play(sX);
+ appIconAnimatorSet.play(sY);
// Fade out the app icon.
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
@@ -671,13 +580,7 @@
*/
private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
Rect bounds = new Rect();
- boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
- && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
- if (isDeepShortcutTextView) {
- // Deep shortcut views have their icon drawn in a sibling view.
- DeepShortcutView view = (DeepShortcutView) v.getParent();
- mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
- } else if (v instanceof BubbleTextView) {
+ if (v instanceof BubbleTextView) {
((BubbleTextView) v).getIconBounds(bounds);
} else {
mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -688,7 +591,7 @@
Matrix matrix = new Matrix();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
- appAnimator.setDuration(APP_LAUNCH_DURATION);
+ appAnimator.setDuration(500);
appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
boolean isFirstFrame = true;
@@ -810,7 +713,7 @@
postAtFrontOfQueueAsynchronously(handler, () -> {
if ((Utilities.getPrefs(mLauncher)
.getBoolean("pref_use_screenshot_for_swipe_up", false)
- && mLauncher.getStateManager().getState().overviewUi)
+ && mLauncher.isInState(LauncherState.OVERVIEW))
|| !launcherIsATargetWithMode(targets, MODE_OPENING)) {
// We use a separate transition for Overview mode. And we can skip the
// animation in cases where Launcher is not in the set of opening targets.
@@ -847,7 +750,7 @@
Matrix matrix = new Matrix();
float height = mLauncher.getDeviceProfile().heightPx;
float width = mLauncher.getDeviceProfile().widthPx;
- float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
+ float endX = (Utilities.isRtl(mLauncher.getResources()) ? -width : width) * 1.16f;
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
diff --git a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
index ab9234b..aec2869 100644
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
@@ -27,20 +27,11 @@
*/
public class LauncherTransitionAnimator {
- private final MutableBoolean mLauncherAnimCancelState;
-
private AnimatorSet mAnimatorSet;
private Animator mLauncherAnimator;
private Animator mWindowAnimator;
LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
- this(launcherAnimator, windowAnimator, new MutableBoolean(false));
- }
-
-
- LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator,
- MutableBoolean launcherAnimCancelState) {
- mLauncherAnimCancelState = launcherAnimCancelState;
if (launcherAnimator != null) {
mLauncherAnimator = launcherAnimator;
}
@@ -59,7 +50,6 @@
public void cancel() {
mAnimatorSet.cancel();
- mLauncherAnimCancelState.value = true;
}
public boolean isRunning() {
@@ -68,7 +58,6 @@
public void finishLauncherAnimation() {
if (mLauncherAnimator != null) {
- mLauncherAnimCancelState.value = true;
mLauncherAnimator.end();
}
}
diff --git a/quickstep/src/com/android/launcher3/MutableBoolean.java b/quickstep/src/com/android/launcher3/MutableBoolean.java
deleted file mode 100644
index 7538217..0000000
--- a/quickstep/src/com/android/launcher3/MutableBoolean.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-public class MutableBoolean {
- public boolean value;
-
- public MutableBoolean(boolean value) {
- this.value = value;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 31261d9..426fe35 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,7 +25,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -33,8 +32,7 @@
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
- | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -59,8 +57,7 @@
@Override
public String getDescription(Launcher launcher) {
- AllAppsContainerView appsView = launcher.getAppsView();
- return appsView.getDescription();
+ return launcher.getString(R.string.all_apps_button_label);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 97ac3e6..541c6bb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -31,12 +31,13 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.VerticalSwipeController;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
class EventLogTags {
private EventLogTags() {
@@ -142,6 +143,11 @@
}
@Override
+ protected void initSprings() {
+ mSpringHandlers = new SpringAnimationHandler[0];
+ }
+
+ @Override
protected float getShiftRange() {
return getShiftRange(mLauncher);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
deleted file mode 100644
index 9541d0d..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import com.android.launcher3.Launcher;
-import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Extension of overview state used for QuickScrub
- */
-public class FastOverviewState extends OverviewState {
-
- private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
- | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
-
- private static final boolean DEBUG_DIFFERENT_UI = false;
-
- public FastOverviewState(int id) {
- super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
- }
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- super.onStateTransitionEnd(launcher);
- RecentsView recentsView = launcher.getOverviewPanel();
- recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
- }
-
- @Override
- public float getHoseatAlpha(Launcher launcher) {
- if (DEBUG_DIFFERENT_UI) {
- return 0;
- }
- return super.getHoseatAlpha(launcher);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java b/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
new file mode 100644
index 0000000..2d5eb5a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.TouchInteractionService;
+
+/**
+ * Consumes touches when quick scrub is enabled.
+ */
+public class IgnoreTouchesInQuickScrub implements TouchController {
+
+ public IgnoreTouchesInQuickScrub() {
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return true;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return TouchInteractionService.isQuickScrubEnabled();
+ }
+}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
similarity index 78%
rename from src/com/android/launcher3/views/OptionsPopupView.java
rename to quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
index 21b6773..c089d06 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.views;
+package com.android.launcher3.uioverrides;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -29,7 +29,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.Toast;
@@ -43,22 +42,19 @@
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.ColorScrim;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.graphics.GradientView;
import com.android.launcher3.widget.WidgetsFullSheet;
/**
* Popup shown on long pressing an empty space in launcher
*/
-public class OptionsPopupView extends AbstractFloatingView
- implements OnClickListener, OnLongClickListener {
+public class OptionsPopupView extends AbstractFloatingView implements OnClickListener {
private final float mOutlineRadius;
private final Launcher mLauncher;
private final PointF mTouchPoint = new PointF();
- private final ColorScrim mScrim;
+ private final GradientView mGradientView;
protected Animator mOpenCloseAnimator;
@@ -79,55 +75,39 @@
});
mLauncher = Launcher.getLauncher(context);
- mScrim = ColorScrim.createExtractedColorScrim(this);
+
+ mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
+ R.layout.widgets_bottom_sheet_scrim, mLauncher.getDragLayer(), false);
+ mGradientView.setProgress(1, false);
+ mGradientView.setAlpha(0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- attachListeners(findViewById(R.id.wallpaper_button));
- attachListeners(findViewById(R.id.widget_button));
- attachListeners(findViewById(R.id.settings_button));
- }
-
- private void attachListeners(View view) {
- view.setOnClickListener(this);
- view.setOnLongClickListener(this);
+ findViewById(R.id.wallpaper_button).setOnClickListener(this);
+ findViewById(R.id.widget_button).setOnClickListener(this);
+ findViewById(R.id.settings_button).setOnClickListener(this);
}
@Override
public void onClick(View view) {
- handleViewClick(view, Action.Touch.TAP);
- }
-
- @Override
- public boolean onLongClick(View view) {
- return handleViewClick(view, Action.Touch.LONGPRESS);
- }
-
- private boolean handleViewClick(View view, int action) {
if (view.getId() == R.id.wallpaper_button) {
mLauncher.onClickWallpaperPicker(null);
- logTap(action, ControlType.WALLPAPER_BUTTON);
close(true);
- return true;
} else if (view.getId() == R.id.widget_button) {
- logTap(action, ControlType.WIDGETS_BUTTON);
- if (onWidgetsClicked(mLauncher)) {
+ if (mLauncher.getPackageManager().isSafeMode()) {
+ Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ } else {
+ WidgetsFullSheet.show(mLauncher, true /* animated */);
close(true);
- return true;
}
} else if (view.getId() == R.id.settings_button) {
- startSettings(mLauncher);
- logTap(action, ControlType.SETTINGS_BUTTON);
+ mLauncher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(mLauncher.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
close(true);
- return true;
}
- return false;
- }
-
- private void logTap(int action, int controlType) {
- mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
}
@Override
@@ -169,7 +149,7 @@
fadeOut.setInterpolator(Interpolators.DEACCEL);
closeAnim.play(fadeOut);
- Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 0);
+ Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 0);
gradientAlpha.setInterpolator(Interpolators.DEACCEL);
closeAnim.play(gradientAlpha);
@@ -197,6 +177,7 @@
}
mIsOpen = false;
mLauncher.getDragLayer().removeView(this);
+ mLauncher.getDragLayer().removeView(mGradientView);
}
@Override
@@ -232,7 +213,7 @@
.createRevealAnimator(this, false);
openAnim.play(revealAnim);
- Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 1);
+ Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 1);
gradientAlpha.setInterpolator(Interpolators.ACCEL);
openAnim.play(gradientAlpha);
@@ -288,23 +269,8 @@
lp.y = Utilities.boundToRange((int) (y - height / 2), insets.top + margin,
maxHeight - insets.bottom - height - margin);
+ launcher.getDragLayer().addView(view.mGradientView);
launcher.getDragLayer().addView(view);
view.animateOpen();
}
-
- public static boolean onWidgetsClicked(Launcher launcher) {
- if (launcher.getPackageManager().isSafeMode()) {
- Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
- return false;
- } else {
- WidgetsFullSheet.show(launcher, true /* animated */);
- return true;
- }
- }
-
- public static void startSettings(Launcher launcher) {
- launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
- .setPackage(launcher.getPackageName())
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index abb4ecf..9ba2308 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,17 +16,20 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import android.content.Context;
import android.graphics.Rect;
import android.view.View;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
/**
* Definition for overview state
@@ -34,26 +37,27 @@
public class OverviewState extends LauncherState {
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
+ | FLAG_DISABLE_RESTORE;
public OverviewState(int id) {
- this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
- }
-
- protected OverviewState(int id, int transitionDuration, int stateFlags) {
- super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+ super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
}
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
Rect pageRect = new Rect();
- RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
+ RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
+ RecentsView rv = launcher.getOverviewPanel();
if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
return super.getWorkspaceScaleAndTranslation(launcher);
}
- return getScaleAndTranslationForPageRect(launcher, pageRect);
+ float overlap = 0;
+ if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
+ overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
+ }
+ return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
}
@Override
@@ -69,8 +73,8 @@
}
@Override
- public void onStateTransitionEnd(Launcher launcher) {
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
+ public float getVerticalProgress(Launcher launcher) {
+ return getVerticalProgress(launcher.getDeviceProfile(), launcher);
}
@Override
@@ -79,25 +83,51 @@
}
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- return new PageAlphaProvider(DEACCEL_2) {
+ final int centerPage = launcher.getWorkspace().getNextPage();
+ return new PageAlphaProvider(ACCEL_2) {
@Override
public float getPageAlpha(int pageIndex) {
- return 0;
+ return pageIndex != centerPage ? 0 : 1f;
}
};
}
- public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
+ public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
+ Rect pageRect) {
Workspace ws = launcher.getWorkspace();
float childWidth = ws.getNormalChildWidth();
+ float childHeight = ws.getNormalChildHeight();
- float scale = pageRect.width() / childWidth;
+ float scale = pageRect.height() / childHeight;
Rect insets = launcher.getDragLayer().getInsets();
float halfHeight = ws.getExpectedHeight() / 2;
float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
float translationY = pageRect.top - childTop;
- return new float[] {scale, 0, translationY};
+ // Align the workspace horizontally centered with the task rect
+ float halfWidth = ws.getExpectedWidth() / 2;
+ float childCenter = halfWidth -
+ scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
+ float translationX = pageRect.centerX() - childCenter;
+
+ if (Utilities.isRtl(launcher.getResources())) {
+ translationX -= offsetX / scale;
+ } else {
+ translationX += offsetX / scale;
+ }
+
+ return new float[] {scale, translationX, translationY};
+ }
+
+ public static float getVerticalProgress(DeviceProfile grid, Context context) {
+ if (!grid.isVerticalBarLayout()) {
+ return 1f;
+ }
+
+ float total = grid.heightPx;
+ float searchHeight = total - grid.availableHeightPx +
+ context.getResources().getDimension(R.dimen.all_apps_search_box_full_height);
+ return 1 - (searchHeight / total);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index c8b54ad..468a561 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -15,14 +15,10 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
@@ -36,13 +32,19 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
-import com.android.quickstep.PendingAnimation;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
/**
* Touch controller for swipe interaction in Overview state
@@ -63,7 +65,6 @@
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
- private PendingAnimation mPendingAnimation;
private AnimatorPlaybackController mCurrentAnimation;
private boolean mCurrentAnimationIsGoingUp;
@@ -131,21 +132,28 @@
mTaskBeingDragged = null;
mSwipeDownEnabled = true;
- View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
- if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
- // The tile can be dragged down to open the task.
- mTaskBeingDragged = (TaskView) view;
+ int currentPage = mRecentsView.getCurrentPage();
+ if (currentPage == 0) {
+ // User is on home tile
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
- mStartingTarget = LauncherLogProto.ItemType.TASK;
- } else if (isEventOverHotseat(ev)) {
- // The hotseat is being dragged
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
- mSwipeDownEnabled = false;
- mStartingTarget = ContainerType.HOTSEAT;
} else {
- mNoIntercept = true;
- mStartingTarget = ContainerType.WORKSPACE;
- return false;
+ View view = mRecentsView.getChildAt(currentPage);
+ if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
+ view instanceof TaskView) {
+ // The tile can be dragged down to open the task.
+ mTaskBeingDragged = (TaskView) view;
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ mStartingTarget = LauncherLogProto.ItemType.TASK;
+ } else if (isEventOverHotseat(ev)) {
+ // The hotseat is being dragged
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ mSwipeDownEnabled = false;
+ mStartingTarget = ContainerType.HOTSEAT;
+ } else {
+ mNoIntercept = true;
+ mStartingTarget = ContainerType.WORKSPACE;
+ return false;
+ }
}
}
@@ -177,11 +185,6 @@
if (mCurrentAnimation != null) {
mCurrentAnimation.setPlayFraction(0);
}
- if (mPendingAnimation != null) {
- mPendingAnimation.finish(false);
- mPendingAnimation = null;
- }
-
mCurrentAnimationIsGoingUp = goingUp;
float range = mLauncher.getAllAppsController().getShiftRange();
long maxDuration = (long) (2 * range);
@@ -198,11 +201,19 @@
}
} else {
if (goingUp) {
- mPendingAnimation = mRecentsView
- .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
- mCurrentAnimation = AnimatorPlaybackController
- .wrap(mPendingAnimation.anim, maxDuration);
- mEndDisplacement = -mTaskBeingDragged.getHeight();
+ AnimatorSet anim = new AnimatorSet();
+ ObjectAnimator translate = ObjectAnimator.ofFloat(
+ mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
+ translate.setInterpolator(LINEAR);
+ translate.setDuration(maxDuration);
+ anim.play(translate);
+
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
+ alpha.setInterpolator(DEACCEL_1_5);
+ alpha.setDuration(maxDuration);
+ anim.play(alpha);
+ mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+ mEndDisplacement = -mTaskBeingDragged.getBottom();
} else {
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
@@ -288,17 +299,15 @@
}
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
- if (mPendingAnimation != null) {
- mPendingAnimation.finish(wasSuccess);
- mPendingAnimation = null;
- }
if (mTaskBeingDragged == null) {
LauncherState state = wasSuccess ?
(mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
mLauncher.getStateManager().goToState(state, false);
} else if (wasSuccess) {
- if (!mCurrentAnimationIsGoingUp) {
+ if (mCurrentAnimationIsGoingUp) {
+ mRecentsView.onTaskDismissed(mTaskBeingDragged);
+ } else {
mTaskBeingDragged.launchTask(false);
mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index b7f79b3..7d371e9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,7 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -28,71 +28,84 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final RecentsView mRecentsView;
+ private final WorkspaceCard mWorkspaceCard;
private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
// The fraction representing the visibility of the RecentsView. This allows delaying the
// overall transition while the RecentsView is being shown or hidden.
private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
- private boolean mIsRecentsSlidingInOrOut;
+ private boolean mIsRecentsScrollingToFirstTask;
public RecentsViewStateController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
+ mRecentsView.setStateController(this);
+
+ mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
+ mWorkspaceCard.setup(launcher);
}
@Override
public void setState(LauncherState state) {
- setVisibility(state.overviewUi);
- setTransitionProgress(state.overviewUi ? 1 : 0);
- if (state.overviewUi) {
- mRecentsView.resetTaskVisuals();
+ mWorkspaceCard.setWorkspaceScrollingEnabled(state == OVERVIEW);
+ setVisibility(state == OVERVIEW);
+ setTransitionProgress(state == OVERVIEW ? 1 : 0);
+ if (state == OVERVIEW) {
+ for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+ ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
+ }
+ mRecentsView.updateCurveProperties();
}
}
@Override
public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
- LauncherState fromState = mLauncher.getStateManager().getState();
- mIsRecentsSlidingInOrOut = fromState == NORMAL && toState.overviewUi
- || fromState.overviewUi && toState == NORMAL;
+ boolean settingEnabled = Utilities.getPrefs(mLauncher)
+ .getBoolean("pref_scroll_to_first_task", false);
+ mIsRecentsScrollingToFirstTask = mLauncher.isInState(NORMAL) && toState == OVERVIEW
+ && settingEnabled;
+ // TODO: Instead of animating the workspace translationX, move the contents
+ mWorkspaceCard.setWorkspaceScrollingEnabled(mIsRecentsScrollingToFirstTask);
// Scroll to the workspace card before changing to the NORMAL state.
int currPage = mRecentsView.getCurrentPage();
- if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
+ if (toState == NORMAL && currPage != 0 && !config.userControlled) {
int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
int durationPerPage = maxSnapDuration / 10;
int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
mRecentsView.snapToPage(0, snapDuration);
- // Let the snapping animation play for a bit before we translate off screen.
- builder.setStartDelay(snapDuration / 4);
+ builder.setStartDelay(snapDuration);
}
ObjectAnimator progressAnim =
- mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
+ mTransitionProgress.animateToValue(toState == OVERVIEW ? 1 : 0);
progressAnim.setDuration(config.duration);
progressAnim.setInterpolator(Interpolators.LINEAR);
progressAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
+ mWorkspaceCard.setWorkspaceScrollingEnabled(toState == OVERVIEW);
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
}
});
builder.play(progressAnim);
- ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
+ ObjectAnimator visibilityAnim = animateVisibility(toState == OVERVIEW);
visibilityAnim.setDuration(config.duration);
visibilityAnim.setInterpolator(Interpolators.LINEAR);
builder.play(visibilityAnim);
@@ -131,14 +144,11 @@
private void onTransitionProgress() {
applyProgress();
- if (mIsRecentsSlidingInOrOut) {
- float interpolatedProgress = ACCEL.getInterpolation(mTransitionProgress.value);
- // Slide in from the side as we swipe.
- int translation = mRecentsView.getWidth();
- if (mRecentsView.isRtl()) {
- translation = -translation;
- }
- mRecentsView.setTranslationX(translation * (1 - interpolatedProgress));
+ if (mIsRecentsScrollingToFirstTask) {
+ int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
+ int scrollForPage0 = mRecentsView.getScrollForPage(0);
+ mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
+ + (1 - mTransitionProgress.value) * scrollForPage0));
}
}
@@ -148,9 +158,5 @@
private void applyProgress() {
mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
- if (mIsRecentsSlidingInOrOut) {
- // While animating into recents, update the visible task data as needed
- mRecentsView.loadVisibleTaskData();
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index c8d75dc..2695054 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.animation.Animator;
@@ -26,6 +27,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.support.animation.SpringAnimation;
import android.util.Log;
import android.view.MotionEvent;
@@ -36,9 +38,11 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -48,6 +52,8 @@
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TouchInteractionService;
+import java.util.ArrayList;
+
/**
* Handles vertical touch gesture on the DragLayer
*/
@@ -106,6 +112,8 @@
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
+ private SpringAnimationHandler[] mSpringHandlers;
+
public TwoStepSwipeController(Launcher l) {
mLauncher = l;
mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
@@ -148,6 +156,29 @@
}
}
+ private void initSprings() {
+ AllAppsContainerView appsView = mLauncher.getAppsView();
+
+ SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+ if (handler == null) {
+ mSpringHandlers = new SpringAnimationHandler[0];
+ return;
+ }
+
+ ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+ handlers.add(handler);
+
+ SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+ if (searchSpring != null) {
+ SpringAnimationHandler searchHandler =
+ new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+ searchHandler.add(searchSpring, true /* setDefaultValues */);
+ handlers.add(searchHandler);
+ }
+
+ mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+ }
+
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -183,6 +214,10 @@
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
+
+ if (mSpringHandlers == null) {
+ initSprings();
+ }
}
if (mNoIntercept) {
@@ -195,6 +230,9 @@
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.addMovement(ev);
+ }
return mDetector.onTouchEvent(ev);
}
@@ -245,6 +283,10 @@
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
updatePauseDetectorRangeFlag();
}
+
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.skipToEnd();
+ }
}
private float getShiftRange() {
@@ -287,6 +329,13 @@
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
}
+ if (fling && targetState == ALL_APPS) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+ h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+ }
+ }
+
float endProgress;
if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 9051cfb..7bd4366 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,29 +18,37 @@
import static com.android.launcher3.LauncherState.NORMAL;
-import android.view.View;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PointF;
import android.view.View.AccessibilityDelegate;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
+import com.android.systemui.shared.recents.view.RecentsTransition;
public class UiFactory {
+ private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
+ "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+
+ public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
+
public static TouchController[] createTouchControllers(Launcher launcher) {
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
return new TouchController[] {
+ new IgnoreTouchesInQuickScrub(),
new EdgeSwipeController(launcher),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
} else {
return new TouchController[] {
+ new IgnoreTouchesInQuickScrub(),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
}
@@ -56,40 +64,28 @@
new RecentsViewStateController(launcher)};
}
+ public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
+ OptionsPopupView.show(launcher, touchPoint.x, touchPoint.y);
+ }
+
public static void onLauncherStateOrFocusChanged(Launcher launcher) {
- boolean shouldBackButtonBeVisible = launcher == null
- || !launcher.isInState(NORMAL)
- || !launcher.hasWindowFocus();
- if (!shouldBackButtonBeVisible) {
- // Show the back button if there is a floating view visible.
- DragLayer dragLayer = launcher.getDragLayer();
- for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = dragLayer.getChildAt(i);
- if (child instanceof AbstractFloatingView) {
- shouldBackButtonBeVisible = true;
- break;
- }
- }
+ OverviewInteractionState.setBackButtonVisible(launcher, launcher == null
+ || !launcher.isInState(NORMAL) || !launcher.hasWindowFocus());
+ }
+
+ public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
+ BitmapRenderer renderer) {
+ if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
+ return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
+ } else {
+ Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ renderer.render(new Canvas(result));
+ return result;
}
- OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
}
public static void resetOverview(Launcher launcher) {
RecentsView recents = launcher.getOverviewPanel();
recents.reset();
}
-
- public static void onStart(Launcher launcher) {
- RecentsModel model = RecentsModel.getInstance(launcher);
- if (model != null) {
- model.onStart();
- }
- }
-
- public static void onTrimMemory(Launcher launcher, int level) {
- RecentsModel model = RecentsModel.getInstance(launcher);
- if (model != null) {
- model.onTrimMemory(level);
- }
- }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
new file mode 100644
index 0000000..8533502
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -0,0 +1,127 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.RecentsView.PageCallbacks;
+import com.android.quickstep.RecentsView.ScrollState;
+
+public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
+
+ private final Rect mTempRect = new Rect();
+
+ private Launcher mLauncher;
+ private Workspace mWorkspace;
+
+ private float mLinearInterpolationForPage2 = 1;
+ private float mTranslateXPage0, mTranslateXPage1;
+ private float mExtraScrollShift;
+
+ private boolean mIsWorkspaceScrollingEnabled;
+
+ public WorkspaceCard(Context context) {
+ this(context, null);
+ }
+
+ public WorkspaceCard(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setOnClickListener(this);
+ }
+
+ /**
+ * Draw nothing.
+ */
+ @Override
+ public void draw(Canvas canvas) { }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // Initiate data
+ mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
+ mLauncher.getDeviceProfile(), mLauncher, mTempRect);
+
+ float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
+ mTranslateXPage0 = scale[1];
+ mTranslateXPage1 = OverviewState
+ .getScaleAndTranslationForPageRect(mLauncher,
+ getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
+ mTempRect)[1];
+
+ mExtraScrollShift = 0;
+ if (mWorkspace != null && getWidth() > 0) {
+ float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
+ mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
+ setScaleX(workspaceWidth / getWidth());
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ mLauncher.getStateManager().goToState(NORMAL);
+ }
+
+ public void setup(Launcher launcher) {
+ mLauncher = launcher;
+ mWorkspace = mLauncher.getWorkspace();
+ }
+
+ public void setWorkspaceScrollingEnabled(boolean isEnabled) {
+ mIsWorkspaceScrollingEnabled = isEnabled;
+ }
+
+ @Override
+ public int onPageScroll(ScrollState scrollState) {
+ float factor = scrollState.linearInterpolation;
+ float translateX = scrollState.distanceFromScreenCenter;
+ if (mIsWorkspaceScrollingEnabled) {
+ float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
+ mWorkspace.setTranslationX(shift + mTranslateXPage0);
+ translateX += shift;
+ }
+
+ setTranslationX(translateX);
+
+ // If the workspace card is still the first page, shift all the other pages.
+ if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
+ scrollState.prevPageExtraWidth = 0;
+ } else if (mLinearInterpolationForPage2 > 0) {
+ scrollState.prevPageExtraWidth = mExtraScrollShift *
+ (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
+ } else {
+ scrollState.prevPageExtraWidth = mExtraScrollShift;
+ }
+ return SCROLL_TYPE_WORKSPACE;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 84dfa45..214b3f3 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -77,12 +77,6 @@
}
}
- public void finishAnimation() {
- if (mValueAnimator != null && mValueAnimator.isRunning()) {
- mValueAnimator.end();
- }
- }
-
public ObjectAnimator getCurrentAnimation() {
return mValueAnimator;
}
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
index 5871a6d..b3ebd77 100644
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -23,7 +23,6 @@
public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
protected Runnable mGestureEndCallback;
- protected boolean mIsGoingToHome;
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
deleted file mode 100644
index b92678a..0000000
--- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.view.Choreographer;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-/**
- * A TouchConsumer which defers all events on the UIThread until the consumer is created.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class DeferredTouchConsumer implements TouchConsumer {
-
- private final VelocityTracker mVelocityTracker;
- private final DeferredTouchProvider mTouchProvider;
-
- private MotionEventQueue mMyQueue;
- private TouchConsumer mTarget;
-
- public DeferredTouchConsumer(DeferredTouchProvider touchProvider) {
- mVelocityTracker = VelocityTracker.obtain();
- mTouchProvider = touchProvider;
- }
-
- @Override
- public void accept(MotionEvent event) {
- mTarget.accept(event);
- }
-
- @Override
- public void reset() {
- mTarget.reset();
- }
-
- @Override
- public void updateTouchTracking(int interactionType) {
- mTarget.updateTouchTracking(interactionType);
- }
-
- @Override
- public void onQuickScrubEnd() {
- mTarget.onQuickScrubEnd();
- }
-
- @Override
- public void onQuickScrubProgress(float progress) {
- mTarget.onQuickScrubProgress(progress);
- }
-
- @Override
- public void preProcessMotionEvent(MotionEvent ev) {
- mVelocityTracker.addMovement(ev);
- }
-
- @Override
- public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
- mMyQueue = queue;
- return null;
- }
-
- @Override
- public void deferInit() {
- mTarget = mTouchProvider.createTouchConsumer(mVelocityTracker);
- mTarget.getIntrimChoreographer(mMyQueue);
- }
-
- @Override
- public boolean forceToLauncherConsumer() {
- return mTarget.forceToLauncherConsumer();
- }
-
- @Override
- public boolean deferNextEventToMainThread() {
- // If our target is still null, defer the next target as well
- TouchConsumer target = mTarget;
- return target == null ? true : target.deferNextEventToMainThread();
- }
-
- public interface DeferredTouchProvider {
-
- TouchConsumer createTouchConsumer(VelocityTracker tracker);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
deleted file mode 100644
index 22f6e0c..0000000
--- a/quickstep/src/com/android/quickstep/FallbackRecentsView.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.quickstep.views.RecentsView;
-
-public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
-
- public FallbackRecentsView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOverviewStateEnabled(true);
- }
-
- @Override
- protected void onAllTasksRemoved() {
- mActivity.finish();
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- DeviceProfile dp = mActivity.getDeviceProfile();
- Rect padding = getPadding(dp, getContext());
- verticalCenter(padding, dp);
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
-
- public static void verticalCenter(Rect padding, DeviceProfile dp) {
- Rect insets = dp.getInsets();
- int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
- padding.top = insets.top + totalSpace;
- padding.bottom = insets.bottom + totalSpace;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
deleted file mode 100644
index 12757c0..0000000
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.InstantAppInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.util.InstantAppResolver;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of InstantAppResolver using platform APIs
- */
-@SuppressWarnings("unused")
-public class InstantAppResolverImpl extends InstantAppResolver {
-
- private static final String TAG = "InstantAppResolverImpl";
- public static final String COMPONENT_CLASS_MARKER = "@instantapp";
-
- private final PackageManager mPM;
-
- public InstantAppResolverImpl(Context context)
- throws NoSuchMethodException, ClassNotFoundException {
- mPM = context.getPackageManager();
- }
-
- @Override
- public boolean isInstantApp(ApplicationInfo info) {
- return info.isInstantApp();
- }
-
- @Override
- public boolean isInstantApp(AppInfo info) {
- ComponentName cn = info.getTargetComponent();
- return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
- }
-
- @Override
- public List<ApplicationInfo> getInstantApps() {
- try {
- List<ApplicationInfo> result = new ArrayList<>();
- for (InstantAppInfo iai : mPM.getInstantApps()) {
- ApplicationInfo info = iai.getApplicationInfo();
- if (info != null) {
- result.add(info);
- }
- }
- return result;
- } catch (SecurityException se) {
- Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
- } catch (Exception e) {
- Log.e(TAG, "Error calling API: getInstantApps", e);
- }
- return super.getInstantApps();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 94b6faa..6e92d83 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -53,8 +53,6 @@
ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_RESET =
ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
- private static final int ACTION_DEFER_INIT =
- ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
private final EventArray mEmptyArray = new EventArray();
private final Object mExecutionLock = new Object();
@@ -78,10 +76,10 @@
public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
mMainChoreographer = choreographer;
mConsumer = consumer;
+
mCurrentChoreographer = mMainChoreographer;
mCurrentRunnable = mMainFrameCallback;
-
- setInterimChoreographer(consumer.getIntrimChoreographer(this));
+ setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
}
public void setInterimChoreographer(Choreographer choreographer) {
@@ -158,9 +156,6 @@
case ACTION_RESET:
mConsumer.reset();
break;
- case ACTION_DEFER_INIT:
- mConsumer.deferInit();
- break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
}
@@ -209,14 +204,6 @@
queueVirtualAction(ACTION_RESET, 0);
}
- public void deferInit() {
- queueVirtualAction(ACTION_DEFER_INIT, 0);
- }
-
- public TouchConsumer getConsumer() {
- return mConsumer;
- }
-
private static class EventArray extends ArrayList<MotionEvent> {
public int lastEventAction = ACTION_CANCEL;
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 89c9d16..ff7d434 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -17,6 +17,7 @@
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
+import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
import static com.android.quickstep.TouchConsumer.isInteractionQuick;
@@ -50,8 +51,6 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.TouchConsumer.InteractionType;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -320,14 +319,13 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
- mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- mStateCallback.setState(mIsGoingToHome
- ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
}
});
anim.start();
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index f875bb7..431fb30 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -16,20 +16,19 @@
package com.android.quickstep;
import android.annotation.TargetApi;
-import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.UserHandle;
import android.util.LruCache;
import android.util.SparseArray;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.systemui.shared.recents.model.IconLoader;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
@@ -41,13 +40,11 @@
public class NormalizedIconLoader extends IconLoader {
private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
- private final DrawableFactory mDrawableFactory;
private LauncherIcons mLauncherIcons;
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> activityInfoCache) {
super(context, iconCache, activityInfoCache);
- mDrawableFactory = DrawableFactory.get(context);
}
@Override
@@ -56,7 +53,7 @@
BitmapInfo info = mDefaultIcons.get(userId);
if (info == null) {
info = getBitmapInfo(Resources.getSystem()
- .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
+ .getDrawable(android.R.drawable.sym_def_app_icon), userId);
mDefaultIcons.put(userId, info);
}
@@ -65,31 +62,23 @@
}
@Override
- protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
- return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
- false));
+ protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
+ return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
}
- private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
- int primaryColor, boolean isInstantApp) {
+ private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
if (mLauncherIcons == null) {
mLauncherIcons = LauncherIcons.obtain(mContext);
}
- mLauncherIcons.setWrapperBackgroundColor(primaryColor);
// User version code O, so that the icon is always wrapped in an adaptive icon container.
return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
- Build.VERSION_CODES.O, isInstantApp);
+ Build.VERSION_CODES.O);
}
@Override
- protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
- TaskDescription desc) {
- BitmapInfo bitmapInfo = getBitmapInfo(
- activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
- userId,
- desc.getPrimaryColor(),
- activityInfo.applicationInfo.isInstantApp());
- return mDrawableFactory.newIcon(bitmapInfo, activityInfo);
+ protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
+ return createBadgedDrawable(
+ activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
}
}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index c96f6d7..488cd72 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,11 +23,9 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.quickstep.RemoteRunnable.executeSafely;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
@@ -39,9 +37,10 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Build;
+import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Looper;
+import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
@@ -64,20 +63,50 @@
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+class EventLogTags {
+ private EventLogTags() {
+ } // don't instantiate
+
+ /** 524292 sysui_multi_action (content|4) */
+ public static final int SYSUI_MULTI_ACTION = 524292;
+
+ public static void writeSysuiMultiAction(Object[] content) {
+ android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
+ }
+}
+
+class MetricsLogger {
+ private static MetricsLogger sMetricsLogger;
+
+ private static MetricsLogger getLogger() {
+ if (sMetricsLogger == null) {
+ sMetricsLogger = new MetricsLogger();
+ }
+ return sMetricsLogger;
+ }
+
+ protected void saveLog(Object[] rep) {
+ EventLogTags.writeSysuiMultiAction(rep);
+ }
+
+ public void write(LogMaker content) {
+ if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
+ content.setType(4/*MetricsEvent.TYPE_ACTION*/);
+ }
+ saveLog(content.serialize());
+ }
+}
+
/**
* Touch consumer for handling events originating from an activity other than Launcher
*/
-@TargetApi(Build.VERSION_CODES.P)
public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
private static final String TAG = "ActivityTouchConsumer";
private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
- private static final int[] DEFERRED_HIT_TARGETS = DEBUG_SHOW_OVERVIEW_BUTTON
- ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
private final RunningTaskInfo mRunningTask;
private final RecentsModel mRecentsModel;
@@ -86,7 +115,6 @@
private final MainThreadExecutor mMainThreadExecutor;
private final Choreographer mBackgroundThreadChoreographer;
- private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
@@ -96,24 +124,26 @@
private BaseSwipeInteractionHandler mInteractionHandler;
private int mDisplayRotation;
private Rect mStableInsets = new Rect();
+ private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
private VelocityTracker mVelocityTracker;
private MotionEventQueue mEventQueue;
- private boolean mIsGoingToHome;
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
- @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
+ @HitTarget int downHitTarget) {
super(base);
mRunningTask = runningTaskInfo;
mRecentsModel = recentsModel;
mHomeIntent = homeIntent;
- mVelocityTracker = velocityTracker;
+ mVelocityTracker = VelocityTracker.obtain();
mISystemUiProxy = systemUiProxy;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
- mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
+ mDownHitTarget = downHitTarget;
}
@Override
@@ -132,7 +162,7 @@
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
- if (!isUsingScreenShot() && !mIsDeferredDownTarget) {
+ if (!isUsingScreenShot() && mDownHitTarget != HIT_TARGET_BACK) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
@@ -174,7 +204,7 @@
if (isUsingScreenShot()) {
startTouchTrackingForScreenshotAnimation();
- } else if (mIsDeferredDownTarget) {
+ } else if (mDownHitTarget == HIT_TARGET_BACK) {
// If we deferred starting the window animation on touch down, then
// start tracking now
startTouchTrackingForWindowAnimation(ev.getEventTime());
@@ -282,7 +312,7 @@
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
// Create the shared handler
final WindowTransformSwipeHandler handler =
- new WindowTransformSwipeHandler(mRunningTask, this, touchTimeMs);
+ new WindowTransformSwipeHandler(mRunningTask, this);
// Preload the plan
mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -320,6 +350,16 @@
TraceHelper.endSection("RecentsController", "Finishing no handler");
controller.finish(false /* toHome */);
}
+
+ // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
+ // "Recents" activity for app transition tests.
+ final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
+ builder.setPackageName("com.android.systemui");
+ builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
+ "com.android.systemui.recents.RecentsActivity");
+ builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
+ SystemClock.uptimeMillis() - touchTimeMs);
+ mMetricsLogger.write(builder);
}
public void onAnimationCanceled() {
@@ -377,7 +417,6 @@
if (mInteractionHandler != null) {
final BaseSwipeInteractionHandler handler = mInteractionHandler;
mInteractionHandler = null;
- mIsGoingToHome = handler.mIsGoingToHome;
mMainThreadExecutor.execute(handler::reset);
}
}
@@ -432,15 +471,4 @@
}
}
}
-
- @Override
- public boolean forceToLauncherConsumer() {
- return mIsGoingToHome;
- }
-
- @Override
- public boolean deferNextEventToMainThread() {
- // TODO: Consider also check if the eventQueue is using mainThread of not.
- return mInteractionHandler != null;
- }
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
deleted file mode 100644
index 031624a..0000000
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
-
-import android.annotation.TargetApi;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-/**
- * Helper class to handle various atomic commands for switching between Overview.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class OverviewCommandHelper extends InternalStateHandler {
-
- private static final boolean DEBUG_START_FALLBACK_ACTIVITY = DEBUG_SHOW_OVERVIEW_BUTTON;
-
- private final Context mContext;
- private final ActivityManagerWrapper mAM;
-
- public final Intent homeIntent;
- public final ComponentName launcher;
-
- private long mLastToggleTime;
-
- public OverviewCommandHelper(Context context) {
- mContext = context;
- mAM = ActivityManagerWrapper.getInstance();
-
- homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(context.getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
- launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
- // Clear the packageName as system can fail to dedupe it b/64108432
- homeIntent.setComponent(launcher).setPackage(null);
- }
-
- public void onOverviewToggle() {
- if (DEBUG_START_FALLBACK_ACTIVITY) {
- mContext.startActivity(new Intent(mContext, RecentsActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
- return;
- }
-
- long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
- mLastToggleTime = SystemClock.elapsedRealtime();
-
- if (isOverviewAlmostVisible()) {
- boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
- startNonLauncherTask(isQuickTap ? 2 : 1);
- } else {
- Intent intent = addToIntent(new Intent(homeIntent));
- mContext.startActivity(intent);
- initWhenReady();
- }
- }
-
- private void startNonLauncherTask(int backStackCount) {
- for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
- backStackCount--;
- if (backStackCount == 0) {
- mAM.startActivityFromRecents(rti.id, null);
- }
- }
- }
-
- private boolean isOverviewAlmostVisible() {
- if (clearReference()) {
- return true;
- }
- if (!mAM.getRunningTask().topActivity.equals(launcher)) {
- return false;
- }
- Launcher launcher = getLauncher();
- return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
- }
-
- private Launcher getLauncher() {
- return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
- }
-
- @Override
- protected boolean init(Launcher launcher, boolean alreadyOnHome) {
- AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
- launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
- clearReference();
- return false;
- }
-
-}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 4af89bf..3c68281 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,9 +15,7 @@
*/
package com.android.quickstep;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import android.content.Context;
import android.os.Handler;
@@ -62,7 +60,7 @@
}
};
- private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
+ private static int sFlags;
public static void setBackButtonVisible(Context context, boolean visible) {
updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
diff --git a/quickstep/src/com/android/quickstep/PendingAnimation.java b/quickstep/src/com/android/quickstep/PendingAnimation.java
deleted file mode 100644
index d22ef61..0000000
--- a/quickstep/src/com/android/quickstep/PendingAnimation.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.animation.AnimatorSet;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Utility class to keep track of a running animation.
- *
- * This class allows attaching end callbacks to an animation is intended to be used with
- * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
- * AnimationListeners are not properly dispatched.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class PendingAnimation {
-
- private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
-
- public final AnimatorSet anim;
-
- public PendingAnimation(AnimatorSet anim) {
- this.anim = anim;
- }
-
- public void finish(boolean isSuccess) {
- for (Consumer<Boolean> listeners : mEndListeners) {
- listeners.accept(isSuccess);
- }
- mEndListeners.clear();
- }
-
- public void addEndListener(Consumer<Boolean> listener) {
- mEndListeners.add(listener);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index a154b29..f28d51c 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,14 +19,13 @@
import android.view.HapticFeedbackConstants;
import com.android.launcher3.Alarm;
-import com.android.launcher3.BaseActivity;
+import com.android.launcher3.Launcher;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.Utilities;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
* Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -36,7 +35,8 @@
*/
public class QuickScrubController implements OnAlarmListener {
- public static final int QUICK_SWITCH_START_DURATION = 210;
+ public static final int QUICK_SWITCH_START_DURATION = 133;
+ public static final int QUICK_SWITCH_SNAP_DURATION = 120;
private static final boolean ENABLE_AUTO_ADVANCE = true;
private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -47,17 +47,15 @@
private final Alarm mAutoAdvanceAlarm;
private final RecentsView mRecentsView;
- private final BaseActivity mActivity;
+ private final Launcher mLauncher;
private boolean mInQuickScrub;
private int mQuickScrubSection;
- private boolean mStartedFromHome;
+ private int mStartPage;
private boolean mHasAlarmRun;
- private boolean mQuickSwitched;
- private boolean mFinishedTransitionToQuickScrub;
- public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
- mActivity = activity;
+ public QuickScrubController(Launcher launcher, RecentsView recentsView) {
+ mLauncher = launcher;
mRecentsView = recentsView;
if (ENABLE_AUTO_ADVANCE) {
mAutoAdvanceAlarm = new Alarm();
@@ -67,14 +65,10 @@
public void onQuickScrubStart(boolean startingFromHome) {
mInQuickScrub = true;
- mStartedFromHome = startingFromHome;
+ mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
mQuickScrubSection = 0;
mHasAlarmRun = false;
- mQuickSwitched = false;
- mFinishedTransitionToQuickScrub = false;
-
- snapToNextTaskIfAvailable();
- mActivity.getUserEventDispatcher().resetActionDurationMillis();
+ mLauncher.getUserEventDispatcher().resetActionDurationMillis();
}
public void onQuickScrubEnd() {
@@ -84,7 +78,11 @@
}
int page = mRecentsView.getNextPage();
Runnable launchTaskRunnable = () -> {
- ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+ if (page < mRecentsView.getFirstTaskIndex()) {
+ mRecentsView.getPageAt(page).performClick();
+ } else {
+ ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+ }
};
int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
* QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
@@ -95,8 +93,8 @@
// No page move needed, just launch it
launchTaskRunnable.run();
}
- mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
- ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
+ mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+ ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
ContainerType.WORKSPACE : ContainerType.APP);
}
@@ -104,9 +102,7 @@
int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
if (quickScrubSection != mQuickScrubSection) {
int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
- if (mFinishedTransitionToQuickScrub) {
- goToPageWithHaptic(pageToGoTo);
- }
+ goToPageWithHaptic(pageToGoTo);
if (ENABLE_AUTO_ADVANCE) {
if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -120,45 +116,36 @@
}
public void onQuickSwitch() {
- mQuickSwitched = true;
- quickSwitchIfReady();
- }
-
- public void onFinishedTransitionToQuickScrub() {
- mFinishedTransitionToQuickScrub = true;
- quickSwitchIfReady();
- }
-
- /**
- * Immediately launches the current task (which we snapped to in onQuickScrubStart) if we've
- * gotten the onQuickSwitch callback and the transition to quick scrub has completed.
- */
- private void quickSwitchIfReady() {
- if (mQuickSwitched && mFinishedTransitionToQuickScrub) {
- onQuickScrubEnd();
- mActivity.getUserEventDispatcher().logActionOnControl(Touch.FLING,
- ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
- ContainerType.WORKSPACE : ContainerType.APP);
+ for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+ TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
+ if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
+ Runnable launchTaskRunnable = () -> taskView.launchTask(true);
+ if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
+ // Snap to the new page then launch it
+ mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
+ } else {
+ // No need to move page, just launch task directly
+ launchTaskRunnable.run();
+ }
+ break;
+ }
}
+ mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
+ ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+ ContainerType.WORKSPACE : ContainerType.APP);
}
- public void snapToNextTaskIfAvailable() {
- if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
- int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
- goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
+ public void snapToPageForCurrentQuickScrubSection() {
+ if (mInQuickScrub) {
+ goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
}
}
private void goToPageWithHaptic(int pageToGoTo) {
- goToPageWithHaptic(pageToGoTo, -1);
- }
-
- private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
- pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
+ pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
if (pageToGoTo != mRecentsView.getNextPage()) {
- int duration = overrideDuration > -1 ? overrideDuration
- : Math.abs(pageToGoTo - mRecentsView.getNextPage())
- * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+ int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
+ * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
mRecentsView.snapToPage(pageToGoTo, duration);
mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -171,7 +158,7 @@
if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
&& currPage < mRecentsView.getPageCount() - 1) {
goToPageWithHaptic(currPage + 1);
- } else if (mQuickScrubSection == 0 && currPage > 0) {
+ } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
goToPageWithHaptic(currPage - 1);
}
mHasAlarmRun = true;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 598c34d..f92d773 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,29 +15,34 @@
*/
package com.android.quickstep;
+import android.app.ListActivity;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.widget.ArrayAdapter;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
/**
* A simple activity to show the recently launched tasks
*/
-public class RecentsActivity extends BaseActivity {
+public class RecentsActivity extends ListActivity {
+
+ private ArrayAdapter<Task> mAdapter;
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
- // activity.
- LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- setDeviceProfile(appState != null
- ? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
- : new InvariantDeviceProfile(this).getDeviceProfile(this));
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
+ plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+ UserHandle.myUserId());
- setContentView(R.layout.fallback_recents_activity);
+ mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
+ mAdapter.addAll(plan.getTaskStack().getTasks());
+ setListAdapter(mAdapter);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 392b73f..7fe7751 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -53,6 +51,7 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel extends TaskStackChangeListener {
+
// We do not need any synchronization for this variable as its only written on UI thread.
private static RecentsModel INSTANCE;
@@ -84,16 +83,10 @@
private int mTaskChangeId;
private ISystemUiProxy mSystemUiProxy;
private boolean mClearAssistCacheOnStackChange = true;
- private final boolean mPreloadTasksInBackground;
private RecentsModel(Context context) {
mContext = context;
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mPreloadTasksInBackground = !activityManager.isLowRamDevice();
- mMainThreadExecutor = new MainThreadExecutor();
-
Resources res = context.getResources();
mRecentsTaskLoader = new RecentsTaskLoader(mContext,
res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
@@ -107,6 +100,8 @@
}
};
mRecentsTaskLoader.startLoader(mContext);
+
+ mMainThreadExecutor = new MainThreadExecutor();
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
mTaskChangeId = 1;
@@ -167,31 +162,6 @@
}
}
- @Override
- public void onTaskStackChangedBackground() {
- int userId = UserHandle.myUserId();
- if (!mPreloadTasksInBackground || !checkCurrentUserId(userId, false /* debug */)) {
- // TODO: Only register this for the current user
- return;
- }
-
- // Preload a fixed number of task icons/thumbnails in the background
- ActivityManager.RunningTaskInfo runningTaskInfo =
- ActivityManagerWrapper.getInstance().getRunningTask();
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
- launchOpts.numVisibleTasks = 2;
- launchOpts.numVisibleTaskThumbnails = 2;
- launchOpts.onlyLoadForCache = true;
- launchOpts.onlyLoadPausedActivities = true;
- launchOpts.loadThumbnails = true;
- PreloadOptions preloadOpts = new PreloadOptions();
- preloadOpts.loadTitles = false;
- plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
- mRecentsTaskLoader.loadTasks(plan, launchOpts);
- }
-
public boolean isLoadPlanValid(int resultId) {
return mTaskChangeId == resultId;
}
@@ -208,19 +178,6 @@
return mSystemUiProxy;
}
- public void onStart() {
- mRecentsTaskLoader.startLoader(mContext);
- mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
- }
-
- public void onTrimMemory(int level) {
- if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
- // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
- mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
- }
- mRecentsTaskLoader.onTrimMemory(level);
- }
-
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/RecentsRootView.java b/quickstep/src/com/android/quickstep/RecentsRootView.java
deleted file mode 100644
index 3c69dbf..0000000
--- a/quickstep/src/com/android/quickstep/RecentsRootView.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-public class RecentsRootView extends InsettableFrameLayout {
-
- private final BaseActivity mActivity;
-
- public RecentsRootView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mActivity = BaseActivity.fromContext(context);
- }
-
- @TargetApi(23)
- @Override
- protected boolean fitSystemWindows(Rect insets) {
- // Update device profile before notifying the children.
- mActivity.getDeviceProfile().updateInsets(insets);
- setInsets(insets);
- return true; // I'll take it from here
- }
-
- @Override
- public void setInsets(Rect insets) {
- // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
- // modifying child layout params.
- if (!insets.equals(mInsets)) {
- super.setInsets(insets);
- }
- setBackground(insets.top == 0 ? null
- : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
- }
-}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
new file mode 100644
index 0000000..ec0716c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -0,0 +1,564 @@
+/*
+ * 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.quickstep;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.TaskView.CURVE_FACTOR;
+import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
+
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+public class RecentsView extends PagedView implements Insettable {
+
+ private static final Rect sTempStableInsets = new Rect();
+
+ public static final int SCROLL_TYPE_NONE = 0;
+ public static final int SCROLL_TYPE_TASK = 1;
+ public static final int SCROLL_TYPE_WORKSPACE = 2;
+
+ private final Launcher mLauncher;
+ private QuickScrubController mQuickScrubController;
+ private final ScrollState mScrollState = new ScrollState();
+ private boolean mOverviewStateEnabled;
+ private boolean mTaskStackListenerRegistered;
+ private LayoutTransition mLayoutTransition;
+ private Runnable mNextPageSwitchRunnable;
+
+ /**
+ * TODO: Call reloadIdNeeded in onTaskStackChanged.
+ */
+ private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+ for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
+ return;
+ }
+ }
+ }
+ };
+
+ private RecentsViewStateController mStateController;
+ private int mFirstTaskIndex;
+
+ private final RecentsModel mModel;
+ private int mLoadPlanId = -1;
+
+ // Only valid until the launcher state changes to NORMAL
+ private int mRunningTaskId = -1;
+
+ private Bitmap mScrim;
+ private Paint mFadePaint;
+ private Shader mFadeShader;
+ private Matrix mFadeMatrix;
+ private boolean mScrimOnLeft;
+
+ private boolean mFirstTaskIconScaledDown = false;
+
+ public RecentsView(Context context) {
+ this(context, null);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
+ enableFreeScroll(true);
+ setupLayoutTransition();
+ setClipToOutline(true);
+
+ mLauncher = Launcher.getLauncher(context);
+ mQuickScrubController = new QuickScrubController(mLauncher, this);
+ mModel = RecentsModel.getInstance(context);
+
+ mScrollState.isRtl = mIsRtl;
+ }
+
+ public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+ for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+ taskView.setAlpha(1);
+ return taskView;
+ }
+ }
+ return null;
+ }
+
+ private void setupLayoutTransition() {
+ // We want to show layout transitions when pages are deleted, to close the gap.
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+
+ mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ setLayoutTransition(mLayoutTransition);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mFirstTaskIndex = getPageCount();
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ updateTaskStackListenerState();
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile();
+ Rect padding = getPadding(dp, getContext());
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.bottomMargin = padding.bottom;
+ setLayoutParams(lp);
+
+ setPadding(padding.left, padding.top, padding.right, 0);
+
+ if (dp.isVerticalBarLayout()) {
+ boolean wasScrimOnLeft = mScrimOnLeft;
+ mScrimOnLeft = dp.isSeascape();
+
+ if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
+ Drawable scrim = getContext().getDrawable(mScrimOnLeft
+ ? R.drawable.recents_horizontal_scrim_left
+ : R.drawable.recents_horizontal_scrim_right);
+ if (scrim instanceof BitmapDrawable) {
+ mScrim = ((BitmapDrawable) scrim).getBitmap();
+ mFadePaint = new Paint();
+ mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
+ mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
+ mFadeMatrix = new Matrix();
+ } else {
+ mScrim = null;
+ }
+ }
+ } else {
+ mScrim = null;
+ mFadePaint = null;
+ mFadeShader = null;
+ mFadeMatrix = null;
+ }
+ }
+
+ public int getFirstTaskIndex() {
+ return mFirstTaskIndex;
+ }
+
+ public boolean isTaskViewVisible(TaskView tv) {
+ // For now, just check if it's the active task or an adjacent task
+ return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
+ }
+
+ public TaskView getTaskView(int taskId) {
+ for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.getTask().key.id == taskId) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
+ public void setStateController(RecentsViewStateController stateController) {
+ mStateController = stateController;
+ }
+
+ public RecentsViewStateController getStateController() {
+ return mStateController;
+ }
+
+ public void setOverviewStateEnabled(boolean enabled) {
+ mOverviewStateEnabled = enabled;
+ updateTaskStackListenerState();
+ }
+
+ public void setNextPageSwitchRunnable(Runnable r) {
+ mNextPageSwitchRunnable = r;
+ }
+
+ @Override
+ protected void onPageEndTransition() {
+ super.onPageEndTransition();
+ if (mNextPageSwitchRunnable != null) {
+ mNextPageSwitchRunnable.run();
+ mNextPageSwitchRunnable = null;
+ }
+ }
+
+ private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+ if (stack == null) {
+ removeAllViews();
+ return;
+ }
+
+ int oldChildCount = getChildCount();
+
+ // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+ // necessary)
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
+ setLayoutTransition(null);
+
+ final int requiredChildCount = tasks.size() + mFirstTaskIndex;
+ for (int i = getChildCount(); i < requiredChildCount; i++) {
+ final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+ addView(taskView);
+ }
+ while (getChildCount() > requiredChildCount) {
+ final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+ removeView(taskView);
+ loader.unloadTaskData(taskView.getTask());
+ }
+ setLayoutTransition(mLayoutTransition);
+
+ // Rebind and reset all task views
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final Task task = tasks.get(i);
+ final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
+ taskView.bind(task);
+ taskView.resetVisualProperties();
+ loader.loadTaskData(task);
+ }
+ updateCurveProperties();
+ applyIconScale(false /* animate */);
+
+ if (oldChildCount != getChildCount()) {
+ mQuickScrubController.snapToPageForCurrentQuickScrubSection();
+ }
+ }
+
+ private void updateTaskStackListenerState() {
+ boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+ && getWindowVisibility() == VISIBLE;
+ if (registerStackListener != mTaskStackListenerRegistered) {
+ if (registerStackListener) {
+ ActivityManagerWrapper.getInstance()
+ .registerTaskStackListener(mTaskStackListener);
+ reloadIfNeeded();
+ } else {
+ ActivityManagerWrapper.getInstance()
+ .unregisterTaskStackListener(mTaskStackListener);
+ }
+ mTaskStackListenerRegistered = registerStackListener;
+ }
+ }
+
+ private static Rect getPadding(DeviceProfile profile, Context context) {
+ WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
+ Rect padding = new Rect(profile.workspacePadding);
+
+ float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
+ float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
+
+ float overviewHeight, overviewWidth;
+ if (profile.isVerticalBarLayout()) {
+ // Use the same padding on both sides for symmetry.
+ float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
+ float availableHeight = profile.availableHeightPx - padding.top
+ - sTempStableInsets.top - Math.max(padding.bottom,
+ profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context)));
+ float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+ overviewHeight = taskHeight * scaledRatio;
+ overviewWidth = taskWidth * scaledRatio;
+
+ } else {
+ overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - sTempStableInsets.top;
+ overviewWidth = taskWidth * overviewHeight / taskHeight;
+ }
+
+ padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
+ - Math.round(overviewHeight);
+ padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ return padding;
+ }
+
+ /**
+ * Sets the {@param outRect} to match the position of the first tile such that it is scaled
+ * down to match the 2nd taskView.
+ * @return returns the factor which determines the scaling factor for the second task.
+ */
+ public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
+ getPageRect(dp, context, outRect);
+
+ int pageSpacing = context.getResources()
+ .getDimensionPixelSize(R.dimen.recents_page_spacing);
+ float halfScreenWidth = dp.widthPx * 0.5f;
+ float halfPageWidth = outRect.width() * 0.5f;
+ float pageCenter = outRect.right + pageSpacing + halfPageWidth;
+ float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
+ float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+ float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
+
+ float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
+
+ int topMargin = context.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ outRect.top -= topMargin;
+ Utilities.scaleRectAboutCenter(outRect, scale);
+ outRect.top += (int) (scale * topMargin);
+ return linearInterpolation;
+ }
+
+ public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+ Rect targetPadding = getPadding(grid, context);
+ Rect insets = grid.getInsets();
+ outRect.set(
+ targetPadding.left + insets.left,
+ targetPadding.top + insets.top,
+ grid.widthPx - targetPadding.right - insets.right,
+ grid.heightPx - targetPadding.bottom - insets.bottom);
+ outRect.top += context.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ }
+
+ @Override
+ public void computeScroll() {
+ super.computeScroll();
+ updateCurveProperties();
+ }
+
+ /**
+ * Scales and adjusts translation of adjacent pages as if on a curved carousel.
+ */
+ public void updateCurveProperties() {
+ if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
+ return;
+ }
+ final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
+ mScrollState.lastScrollType = SCROLL_TYPE_NONE;
+ final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
+ final int halfScreenWidth = getMeasuredWidth() / 2;
+ final int pageSpacing = mPageSpacing;
+
+ final int pageCount = getPageCount();
+ for (int i = 0; i < pageCount; i++) {
+ View page = getPageAt(i);
+ int pageCenter = page.getLeft() + halfPageWidth;
+ mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
+ float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+ mScrollState.linearInterpolation = Math.min(1,
+ Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
+ mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
+ }
+ }
+
+ public void onTaskDismissed(TaskView taskView) {
+ ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ removeView(taskView);
+ if (getTaskCount() == 0) {
+ mLauncher.getStateManager().goToState(NORMAL);
+ }
+ }
+
+ public void reset() {
+ mRunningTaskId = -1;
+ setCurrentPage(0);
+ }
+
+ public int getTaskCount() {
+ return getChildCount() - mFirstTaskIndex;
+ }
+
+ public int getRunningTaskId() {
+ return mRunningTaskId;
+ }
+
+ /**
+ * Reloads the view if anything in recents changed.
+ */
+ public void reloadIfNeeded() {
+ if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+ mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
+ }
+ }
+
+ /**
+ * Ensures that the first task in the view represents {@param task} and reloads the view
+ * if needed. This allows the swipe-up gesture to assume that the first tile always
+ * corresponds to the correct task.
+ * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+ * is called.
+ * Also scrolls the view to this task
+ */
+ public void showTask(int runningTaskId) {
+ boolean needsReload = false;
+ if (getTaskCount() == 0) {
+ needsReload = true;
+ // Add an empty view for now
+ setLayoutTransition(null);
+ final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+ .inflate(R.layout.task, this, false);
+ addView(taskView, mFirstTaskIndex);
+ setLayoutTransition(mLayoutTransition);
+ }
+ if (!needsReload) {
+ needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+ }
+ if (needsReload) {
+ mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+ }
+ mRunningTaskId = runningTaskId;
+ setCurrentPage(mFirstTaskIndex);
+ if (mCurrentPage >= mFirstTaskIndex) {
+ getPageAt(mCurrentPage).setAlpha(0);
+ }
+ }
+
+ public QuickScrubController getQuickScrubController() {
+ return mQuickScrubController;
+ }
+
+ public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
+ if (mFirstTaskIconScaledDown == isScaledDown) {
+ return;
+ }
+ mFirstTaskIconScaledDown = isScaledDown;
+ applyIconScale(animate);
+ }
+
+ private void applyIconScale(boolean animate) {
+ float scale = mFirstTaskIconScaledDown ? 0 : 1;
+ TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
+ if (firstTask != null) {
+ if (animate) {
+ firstTask.animateIconToScale(scale);
+ } else {
+ firstTask.setIconScale(scale);
+ }
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mScrim == null) {
+ super.draw(canvas);
+ return;
+ }
+
+ final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+ int length = mScrim.getWidth();
+ int height = getHeight();
+ int saveCount = canvas.getSaveCount();
+
+ int scrimLeft;
+ if (mScrimOnLeft) {
+ scrimLeft = getScrollX();
+ } else {
+ scrimLeft = getScrollX() + getWidth() - length;
+ }
+ canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
+ super.draw(canvas);
+
+ mFadeMatrix.setTranslate(scrimLeft, 0);
+ mFadeShader.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFadeShader);
+ canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
+ canvas.restoreToCount(saveCount);
+ }
+
+ public interface PageCallbacks {
+
+ /**
+ * Updates the page UI based on scroll params and returns the type of scroll
+ * effect performed.
+ *
+ * @see #SCROLL_TYPE_NONE
+ * @see #SCROLL_TYPE_TASK
+ * @see #SCROLL_TYPE_WORKSPACE
+ */
+ int onPageScroll(ScrollState scrollState);
+ }
+
+ public static class ScrollState {
+
+ public boolean isRtl;
+ public int lastScrollType;
+
+ public int halfPageWidth;
+ public float distanceFromScreenCenter;
+ public float linearInterpolation;
+
+ public float prevPageExtraWidth;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
new file mode 100644
index 0000000..8425fa3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SimpleTaskView.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A simple view which keeps its size proportional to the display size
+ */
+public class SimpleTaskView extends View {
+
+ private static final Point sTempPoint = new Point();
+
+ public SimpleTaskView(Context context) {
+ super(context);
+ }
+
+ public SimpleTaskView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ getContext().getSystemService(WindowManager.class)
+ .getDefaultDisplay().getRealSize(sTempPoint);
+
+ int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
+ setMeasuredDimension(width, height);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/TaskMenuView.java
similarity index 98%
rename from quickstep/src/com/android/quickstep/views/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/TaskMenuView.java
index 94f440d..6bbcb37 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/TaskMenuView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep.views;
+package com.android.quickstep;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -40,8 +40,6 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.quickstep.TaskSystemShortcut;
-import com.android.quickstep.TaskUtils;
/**
* Contains options for a recent task when long-pressing its icon.
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index eebfb91..75d8619 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,39 +16,27 @@
package com.android.quickstep;
+import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.InstantAppResolver;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.Collections;
-import java.util.List;
import java.util.function.Consumer;
/**
@@ -98,10 +86,9 @@
}
}
- public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener {
+ public static class SplitScreen extends TaskSystemShortcut {
private Handler mHandler;
- private TaskView mTaskView;
public SplitScreen() {
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -117,55 +104,22 @@
if (!task.isDockable) {
return null;
}
- mTaskView = taskView;
return (v -> {
- AbstractFloatingView.closeOpenViews(launcher, true,
- AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
-
- if (ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key.id,
- ActivityOptionsCompat.makeSplitScreenOptions(true))) {
- ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
- try {
- sysUiProxy.onSplitScreenInvoked();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
- return;
- }
-
- final Runnable animStartedListener = () -> {
- mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
+ final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(true);
+ final Consumer<Boolean> resultCallback = success -> {
+ if (success) {
launcher.<RecentsView>getOverviewPanel().removeView(taskView);
- };
+ }
+ };
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
+ task.key, options, resultCallback, mHandler);
- final int[] position = new int[2];
- taskView.getLocationOnScreen(position);
- final int width = (int) (taskView.getWidth() * taskView.getScaleX());
- final int height = (int) (taskView.getHeight() * taskView.getScaleY());
- final Rect taskBounds = new Rect(position[0], position[1],
- position[0] + width, position[1] + height);
-
- Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
- taskBounds.width(), taskBounds.height(), taskView, 1f, Color.BLACK);
- AppTransitionAnimationSpecsFuture future =
- new AppTransitionAnimationSpecsFuture(mHandler) {
- @Override
- public List<AppTransitionAnimationSpecCompat> composeSpecs() {
- return Collections.singletonList(new AppTransitionAnimationSpecCompat(
- task.key.id, thumbnail, taskBounds));
- }
- };
- WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
- future, animStartedListener, mHandler, true /* scaleUp */);
- }
+ // TODO improve transition; see:
+ // DockedFirstAnimationFrameEvent
+ // RecentsTransitionHelper#composeDockAnimationSpec
+ // WindowManagerWrapper#overridePendingAppTransitionMultiThumbFuture
});
}
-
- @Override
- public boolean onPreDraw() {
- mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
- WindowManagerWrapper.getInstance().endProlongedAnimations();
- return true;
- }
}
public static class Pin extends TaskSystemShortcut {
@@ -183,11 +137,7 @@
if (sysUiProxy == null) {
return null;
}
- if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
- return null;
- }
- if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
- // We shouldn't be able to pin while an app is locked.
+ if (!ActivityManagerWrapper.getInstance().isLockToAppEnabled()) {
return null;
}
return view -> {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 87bb53b..9b9c6f2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep.views;
+package com.android.quickstep;
import android.content.Context;
import android.content.res.Configuration;
@@ -33,10 +33,9 @@
import android.util.AttributeSet;
import android.view.View;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -75,7 +74,6 @@
mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
- mPaint.setFilterBitmap(true);
}
public void bind() {
@@ -153,8 +151,7 @@
} else {
final Configuration configuration =
getContext().getApplicationContext().getResources().getConfiguration();
- final DeviceProfile profile = BaseActivity.fromContext(getContext())
- .getDeviceProfile();
+ final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
if (configuration.orientation == mThumbnailData.orientation) {
// If we are in the same orientation as the screenshot, just scale it to the
// width of the task view
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
similarity index 76%
rename from quickstep/src/com/android/quickstep/views/TaskView.java
rename to quickstep/src/com/android/quickstep/TaskView.java
index 7a575ad..b407f75 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.quickstep.views;
+package com.android.quickstep;
+
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
@@ -30,8 +33,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.quickstep.views.RecentsView.PageCallbacks;
-import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.RecentsView.PageCallbacks;
+import com.android.quickstep.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -44,15 +47,17 @@
*/
public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
- /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
- private static final TimeInterpolator CURVE_INTERPOLATOR
- = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
+ /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
+ public static final float CURVE_FACTOR = 0.25f;
+ /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
+ public static final TimeInterpolator CURVE_INTERPOLATOR
+ = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
/**
* The alpha of a black scrim on a page in the carousel as it leaves the screen.
* In the resting position of the carousel, the adjacent pages have about half this scrim.
*/
- private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
+ private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
private static final long SCALE_ICON_DURATION = 120;
@@ -154,16 +159,38 @@
setScaleY(1f);
setTranslationX(0f);
setTranslationY(0f);
- setTranslationZ(0);
setAlpha(1f);
}
@Override
- public void onPageScroll(ScrollState scrollState) {
+ public int onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+ float scale = 1 - curveInterpolation * CURVE_FACTOR;
+ setScaleX(scale);
+ setScaleY(scale);
+
+ // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
+ setTranslationZ(scale);
mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+
+ float translation =
+ scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
+
+ if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
+ // Make sure that the task cards do not overlap with the workspace card
+ float min = scrollState.halfPageWidth * (1 - scale);
+ if (scrollState.isRtl) {
+ setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
+ } else {
+ setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
+ }
+ } else {
+ setTranslationX(translation);
+ }
+ scrollState.prevPageExtraWidth = 0;
+ return SCROLL_TYPE_TASK;
}
private static final class TaskOutlineProvider extends ViewOutlineProvider {
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 768fbda..f35f6a6 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,6 +21,8 @@
import android.view.Choreographer;
import android.view.MotionEvent;
+import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -62,14 +64,4 @@
default Choreographer getIntrimChoreographer(MotionEventQueue queue) {
return null;
}
-
- default void deferInit() { }
-
- default boolean deferNextEventToMainThread() {
- return false;
- }
-
- default boolean forceToLauncherConsumer() {
- return false;
- }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a522ef1..c166292 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,13 +21,15 @@
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.graphics.PointF;
import android.os.Build;
import android.os.Handler;
@@ -38,18 +40,15 @@
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -61,8 +60,6 @@
@TargetApi(Build.VERSION_CODES.O)
public class TouchInteractionService extends Service {
- public static final boolean DEBUG_SHOW_OVERVIEW_BUTTON = false;
-
private static final SparseArray<String> sMotionEventNames;
static {
@@ -120,6 +117,7 @@
@Override
public void onQuickScrubStart() {
mEventQueue.onQuickScrubStart();
+ sQuickScrubEnabled = true;
TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
}
@@ -132,37 +130,35 @@
public void onQuickScrubEnd() {
mEventQueue.onQuickScrubEnd();
TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
+ sQuickScrubEnabled = false;
}
-
- @Override
- public void onOverviewToggle() {
- mOverviewCommandHelper.onOverviewToggle();
- }
-
- @Override
- public void onOverviewShown(boolean triggeredFromAltTab) { }
-
- @Override
- public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
};
private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
private static boolean sConnected = false;
+ private static boolean sQuickScrubEnabled = false;
public static boolean isConnected() {
return sConnected;
}
+ public static boolean isQuickScrubEnabled() {
+ return sQuickScrubEnabled;
+ }
+
private ActivityManagerWrapper mAM;
+ private RunningTaskInfo mRunningTask;
private RecentsModel mRecentsModel;
+ private Intent mHomeIntent;
+ private ComponentName mLauncher;
private MotionEventQueue mEventQueue;
private MainThreadExecutor mMainThreadExecutor;
private ISystemUiProxy mISystemUiProxy;
- private OverviewCommandHelper mOverviewCommandHelper;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
+ private MotionEventQueue mNoOpEventQueue;
@Override
public void onCreate() {
@@ -170,9 +166,19 @@
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.getInstance(this);
mMainThreadExecutor = new MainThreadExecutor();
- mOverviewCommandHelper = new OverviewCommandHelper(this);
+
+ mHomeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
+ mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
+ // Clear the packageName as system can fail to dedupe it b/64108432
+ mHomeIntent.setComponent(mLauncher).setPackage(null);
+
mMainThreadChoreographer = Choreographer.getInstance();
- mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+ mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+ mEventQueue = mNoOpEventQueue;
sConnected = true;
@@ -184,6 +190,7 @@
@Override
public void onDestroy() {
sConnected = false;
+ sQuickScrubEnabled = false;
super.onDestroy();
}
@@ -194,45 +201,31 @@
}
private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
+ mRunningTask = mAM.getRunningTask();
+
mEventQueue.reset();
- TouchConsumer oldConsumer = mEventQueue.getConsumer();
- if (oldConsumer.deferNextEventToMainThread()) {
+
+ if (mRunningTask == null) {
+ mEventQueue = mNoOpEventQueue;
+ } else if (mRunningTask.topActivity.equals(mLauncher)) {
+ mEventQueue = getLauncherEventQueue();
+ } else {
mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
- new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
- oldConsumer.forceToLauncherConsumer(), v)));
- mEventQueue.deferInit();
- } else {
- mEventQueue = new MotionEventQueue(
- mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
+ new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
+ mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
+ mBackgroundThreadChoreographer, downHitTarget));
}
}
- private TouchConsumer getCurrentTouchConsumer(
- @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
- RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
-
- if (runningTaskInfo == null && !forceToLauncher) {
- return mNoOpTouchConsumer;
- } else if (forceToLauncher ||
- runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
- return getLauncherConsumer();
- } else {
- if (tracker == null) {
- tracker = VelocityTracker.obtain();
- }
- return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
- mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
- mBackgroundThreadChoreographer, downHitTarget, tracker);
- }
- }
-
- private TouchConsumer getLauncherConsumer() {
+ private MotionEventQueue getLauncherEventQueue() {
Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
if (launcher == null) {
- return mNoOpTouchConsumer;
+ return mNoOpEventQueue;
}
+
View target = launcher.getDragLayer();
- return new LauncherTouchConsumer(launcher, target);
+ return new MotionEventQueue(mMainThreadChoreographer,
+ new LauncherTouchConsumer(launcher, target));
}
private static class LauncherTouchConsumer implements TouchConsumer {
@@ -317,12 +310,13 @@
if (TouchConsumer.isInteractionQuick(interactionType)) {
Runnable action = () -> {
Runnable onComplete = null;
- if (interactionType == INTERACTION_QUICK_SWITCH) {
+ if (interactionType == INTERACTION_QUICK_SCRUB) {
+ mQuickScrubController.onQuickScrubStart(true);
+ } else if (interactionType == INTERACTION_QUICK_SWITCH) {
onComplete = mQuickScrubController::onQuickSwitch;
}
- LauncherState fromState = mLauncher.getStateManager().getState();
- mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
- mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
+ mLauncher.getStateManager().goToState(OVERVIEW, true, 0,
+ QUICK_SWITCH_START_DURATION, onComplete);
};
if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index af09842..19942c3 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,12 +15,9 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@@ -35,17 +32,16 @@
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.metrics.LogMaker;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
@@ -71,12 +67,9 @@
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.quickstep.TouchConsumer.InteractionType;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
@@ -84,40 +77,6 @@
import java.util.StringJoiner;
-class EventLogTags {
- private EventLogTags() {
- } // don't instantiate
-
- /** 524292 sysui_multi_action (content|4) */
- public static final int SYSUI_MULTI_ACTION = 524292;
-
- public static void writeSysuiMultiAction(Object[] content) {
- android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
- }
-}
-
-class MetricsLogger {
- private static MetricsLogger sMetricsLogger;
-
- private static MetricsLogger getLogger() {
- if (sMetricsLogger == null) {
- sMetricsLogger = new MetricsLogger();
- }
- return sMetricsLogger;
- }
-
- protected void saveLog(Object[] rep) {
- EventLogTags.writeSysuiMultiAction(rep);
- }
-
- public void write(LogMaker content) {
- if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
- content.setType(4/*MetricsEvent.TYPE_ACTION*/);
- }
- saveLog(content.serialize());
- }
-}
-
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -230,17 +189,11 @@
private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
private Matrix mTmpMatrix = new Matrix();
- private final long mTouchTimeMs;
- private long mLauncherFrameDrawnTime;
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
- WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs) {
+ WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
mContext = context;
mRunningTaskId = runningTaskInfo.id;
- mTouchTimeMs = touchTimeMs;
- // Register the input consumer on the UI thread, to ensure that it runs after any pending
- // unregister calls
- mMainExecutor.execute(mInputConsumer::registerInputConsumer);
+ mInputConsumer.registerInputConsumer();
initStateCallbacks();
}
@@ -370,8 +323,7 @@
// For the duration of the gesture, lock the screen orientation to ensure that we do not
// rotate mid-quickscrub
- mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
-
+ mLauncher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
mRecentsView = mLauncher.getOverviewPanel();
mQuickScrubController = mRecentsView.getQuickScrubController();
mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
@@ -464,26 +416,11 @@
if (mLauncherDrawnCallback != null) {
mLauncherDrawnCallback.run();
}
- mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
private void initializeLauncherAnimationController() {
mLauncherLayoutListener.setHandler(this);
onLauncherLayoutChanged();
-
- final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
- // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
- // "Recents" activity for app transition tests for the app-to-recents case.
- final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
- builder.setPackageName("com.android.systemui");
- builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
- "com.android.systemui.recents.RecentsActivity");
- builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
- transitionDelay);
- mMetricsLogger.write(builder);
- if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
- }
}
public void updateInteractionType(@InteractionType int interactionType) {
@@ -505,8 +442,6 @@
}
private void onQuickInteractionStart() {
- mLauncher.getStateManager().goToState(FAST_OVERVIEW,
- mWasLauncherAlreadyVisible || mGestureStarted);
mQuickScrubController.onQuickScrubStart(false);
}
@@ -574,13 +509,8 @@
for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
if (app.mode == MODE_CLOSING) {
transaction.setMatrix(app.leash, mTmpMatrix)
- .setWindowCrop(app.leash, mClipRect);
-
- if (app.isNotInRecents) {
- transaction.setAlpha(app.leash, 1 - shift);
- }
-
- transaction.show(app.leash);
+ .setWindowCrop(app.leash, mClipRect)
+ .show(app.leash);
}
}
transaction.apply();
@@ -595,8 +525,9 @@
mLauncherTransitionController.setPlayFraction(shift);
// Make sure the window follows the first task if it moves, e.g. during quick scrub.
- View firstTask = mRecentsView.getPageAt(0);
- int scrollForFirstTask = mRecentsView.getScrollForPage(0);
+ int firstTaskIndex = mRecentsView.getFirstTaskIndex();
+ View firstTask = mRecentsView.getPageAt(firstTaskIndex);
+ int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
if (offsetFromFirstTask != 0) {
synchronized (mTargetRect) {
@@ -727,14 +658,13 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
- mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(mIsGoingToHome ?
- STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
+ setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
}
});
anim.start();
@@ -755,7 +685,7 @@
}
private void invalidateHandler() {
- mCurrentShift.finishAnimation();
+ mCurrentShift.cancelAnimation();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
@@ -771,8 +701,7 @@
mLauncherLayoutListener.close(false);
// Restore the requested orientation to the user preference after the gesture has ended
- mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
-
+ mLauncher.updateRequestedOrientation();
mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
deleted file mode 100644
index 7989e84..0000000
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-/**
- * {@link RecentsView} used in Launcher activity
- */
-public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
-
- private Bitmap mScrim;
- private Paint mFadePaint;
- private Shader mFadeShader;
- private Matrix mFadeMatrix;
- private boolean mScrimOnLeft;
-
- public LauncherRecentsView(Context context) {
- this(context, null);
- }
-
- public LauncherRecentsView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- DeviceProfile dp = mActivity.getDeviceProfile();
- Rect padding = getPadding(dp, getContext());
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- lp.bottomMargin = padding.bottom;
- setLayoutParams(lp);
-
- setPadding(padding.left, padding.top, padding.right, 0);
-
- if (dp.isVerticalBarLayout()) {
- boolean wasScrimOnLeft = mScrimOnLeft;
- mScrimOnLeft = dp.isSeascape();
-
- if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
- Drawable scrim = getContext().getDrawable(mScrimOnLeft
- ? R.drawable.recents_horizontal_scrim_left
- : R.drawable.recents_horizontal_scrim_right);
- if (scrim instanceof BitmapDrawable) {
- mScrim = ((BitmapDrawable) scrim).getBitmap();
- mFadePaint = new Paint();
- mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
- mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
- mFadeMatrix = new Matrix();
- } else {
- mScrim = null;
- }
- }
- } else {
- mScrim = null;
- mFadePaint = null;
- mFadeShader = null;
- mFadeMatrix = null;
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mScrim == null) {
- super.draw(canvas);
- return;
- }
-
- final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
- int length = mScrim.getWidth();
- int height = getHeight();
- int saveCount = canvas.getSaveCount();
-
- int scrimLeft;
- if (mScrimOnLeft) {
- scrimLeft = getScrollX();
- } else {
- scrimLeft = getScrollX() + getWidth() - length;
- }
- canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
- super.draw(canvas);
-
- mFadeMatrix.setTranslate(scrimLeft, 0);
- mFadeShader.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFadeShader);
- canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
- canvas.restoreToCount(saveCount);
- }
-
- @Override
- protected void onAllTasksRemoved() {
- mActivity.getStateManager().goToState(NORMAL);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
deleted file mode 100644
index 23e6e5b..0000000
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ /dev/null
@@ -1,588 +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.quickstep.views;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.quickstep.PendingAnimation;
-import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.RecentsModel;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * A list of recent tasks.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity>
- extends PagedView implements OnSharedPreferenceChangeListener {
-
- private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
-
- private static final Rect sTempStableInsets = new Rect();
-
- protected final T mActivity;
- private final QuickScrubController mQuickScrubController;
- private final float mFastFlingVelocity;
- private final RecentsModel mModel;
-
- private final ScrollState mScrollState = new ScrollState();
- // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
- private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
-
- /**
- * TODO: Call reloadIdNeeded in onTaskStackChanged.
- */
- private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
- for (int i = 0; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
- return;
- }
- }
- }
- };
-
- private int mLoadPlanId = -1;
-
- // Only valid until the launcher state changes to NORMAL
- private int mRunningTaskId = -1;
-
- private boolean mFirstTaskIconScaledDown = false;
-
- private boolean mOverviewStateEnabled;
- private boolean mTaskStackListenerRegistered;
- private Runnable mNextPageSwitchRunnable;
-
- private PendingAnimation mPendingAnimation;
-
- public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
- enableFreeScroll(true);
- setClipToOutline(true);
-
- mFastFlingVelocity = getResources()
- .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
- mActivity = (T) BaseActivity.fromContext(context);
- mQuickScrubController = new QuickScrubController(mActivity, this);
- mModel = RecentsModel.getInstance(context);
-
- onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (s.equals(PREF_FLIP_RECENTS)) {
- mIsRtl = Utilities.isRtl(getResources());
- if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
- mIsRtl = !mIsRtl;
- }
- setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
- }
- }
-
- public boolean isRtl() {
- return mIsRtl;
- }
-
- public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
- for (int i = 0; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
- taskView.setAlpha(1);
- return taskView;
- }
- }
- return null;
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- updateTaskStackListenerState();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- updateTaskStackListenerState();
- Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- updateTaskStackListenerState();
- Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onViewRemoved(View child) {
- super.onViewRemoved(child);
-
- // Clear the task data for the removed child if it was visible
- Task task = ((TaskView) child).getTask();
- if (mHasVisibleTaskData.get(task.key.id)) {
- mHasVisibleTaskData.delete(task.key.id);
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
- }
- }
-
- public boolean isTaskViewVisible(TaskView tv) {
- // For now, just check if it's the active task or an adjacent task
- return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
- }
-
- public TaskView getTaskView(int taskId) {
- for (int i = 0; i < getChildCount(); i++) {
- TaskView tv = (TaskView) getChildAt(i);
- if (tv.getTask().key.id == taskId) {
- return tv;
- }
- }
- return null;
- }
-
- public void setOverviewStateEnabled(boolean enabled) {
- mOverviewStateEnabled = enabled;
- updateTaskStackListenerState();
- }
-
- public void setNextPageSwitchRunnable(Runnable r) {
- mNextPageSwitchRunnable = r;
- }
-
- @Override
- protected void onPageEndTransition() {
- super.onPageEndTransition();
- if (mNextPageSwitchRunnable != null) {
- mNextPageSwitchRunnable.run();
- mNextPageSwitchRunnable = null;
- }
- }
-
- private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
- if (mPendingAnimation != null) {
- mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
- return;
- }
- TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
- if (stack == null) {
- removeAllViews();
- return;
- }
-
- int oldChildCount = getChildCount();
-
- // Ensure there are as many views as there are tasks in the stack (adding and trimming as
- // necessary)
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
-
- final int requiredChildCount = tasks.size();
- for (int i = getChildCount(); i < requiredChildCount; i++) {
- final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
- addView(taskView);
- }
- while (getChildCount() > requiredChildCount) {
- final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
- removeView(taskView);
- }
-
- // Unload existing visible task data
- unloadVisibleTaskData();
-
- // Rebind and reset all task views
- for (int i = requiredChildCount - 1; i >= 0; i--) {
- final int pageIndex = requiredChildCount - i - 1;
- final Task task = tasks.get(i);
- final TaskView taskView = (TaskView) getChildAt(pageIndex);
- taskView.bind(task);
- }
- resetTaskVisuals();
- applyIconScale(false /* animate */);
-
- if (oldChildCount != getChildCount()) {
- mQuickScrubController.snapToNextTaskIfAvailable();
- }
- }
-
- public void resetTaskVisuals() {
- for (int i = getChildCount() - 1; i >= 0; i--) {
- ((TaskView) getChildAt(i)).resetVisualProperties();
- }
-
- updateCurveProperties();
- // Update the set of visible task's data
- loadVisibleTaskData();
- }
-
- private void updateTaskStackListenerState() {
- boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
- && getWindowVisibility() == VISIBLE;
- if (registerStackListener != mTaskStackListenerRegistered) {
- if (registerStackListener) {
- ActivityManagerWrapper.getInstance()
- .registerTaskStackListener(mTaskStackListener);
- reloadIfNeeded();
- } else {
- ActivityManagerWrapper.getInstance()
- .unregisterTaskStackListener(mTaskStackListener);
- }
- mTaskStackListenerRegistered = registerStackListener;
- }
- }
-
- protected static Rect getPadding(DeviceProfile profile, Context context) {
- WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
- Rect padding = new Rect(profile.workspacePadding);
-
- float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
- float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
-
- float overviewHeight, overviewWidth;
- if (profile.isVerticalBarLayout()) {
- float scrimLength = context.getResources()
- .getDimension(R.dimen.recents_page_fade_length);
- float maxPadding = Math.max(padding.left, padding.right);
-
- // Use the same padding on both sides for symmetry.
- float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
- float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
- - sTempStableInsets.top;
- float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- overviewHeight = taskHeight * scaledRatio;
- overviewWidth = taskWidth * scaledRatio;
-
- } else {
- overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- - sTempStableInsets.top;
- overviewWidth = taskWidth * overviewHeight / taskHeight;
- }
-
- padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
- - Math.round(overviewHeight);
- padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
- return padding;
- }
-
- public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
- Rect targetPadding = getPadding(grid, context);
- Rect insets = grid.getInsets();
- outRect.set(
- targetPadding.left + insets.left,
- targetPadding.top + insets.top,
- grid.widthPx - targetPadding.right - insets.right,
- grid.heightPx - targetPadding.bottom - insets.bottom);
- outRect.top += context.getResources()
- .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- }
-
- @Override
- protected boolean computeScrollHelper() {
- boolean scrolling = super.computeScrollHelper();
- boolean isFlingingFast = false;
- updateCurveProperties();
- if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
- if (scrolling) {
- // Check if we are flinging quickly to disable high res thumbnail loading
- isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
- }
-
- // After scrolling, update the visible task's data
- loadVisibleTaskData();
- }
-
- // Update the high res thumbnail loader
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
- return scrolling;
- }
-
- /**
- * Scales and adjusts translation of adjacent pages as if on a curved carousel.
- */
- public void updateCurveProperties() {
- if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
- return;
- }
- final int halfPageWidth = getNormalChildWidth() / 2;
- final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
- final int halfScreenWidth = getMeasuredWidth() / 2;
- final int pageSpacing = mPageSpacing;
-
- final int pageCount = getPageCount();
- for (int i = 0; i < pageCount; i++) {
- View page = getPageAt(i);
- float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
- float distanceFromScreenCenter = screenCenter - pageCenter;
- float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
- mScrollState.linearInterpolation = Math.min(1,
- Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
- ((PageCallbacks) page).onPageScroll(mScrollState);
- }
- }
-
- /**
- * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
- * and unloads the associated task data for tasks that are no longer visible.
- */
- public void loadVisibleTaskData() {
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- int centerPageIndex = getPageNearestToCenterOfScreen();
- int lower = Math.max(0, centerPageIndex - 2);
- int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
- int numChildren = getChildCount();
-
- // Update the task data for the in/visible children
- for (int i = 0; i < numChildren; i++) {
- TaskView taskView = (TaskView) getChildAt(i);
- Task task = taskView.getTask();
- boolean visible = lower <= i && i <= upper;
- if (visible) {
- if (!mHasVisibleTaskData.get(task.key.id)) {
- loader.loadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskVisible(task);
- }
- mHasVisibleTaskData.put(task.key.id, visible);
- } else {
- if (mHasVisibleTaskData.get(task.key.id)) {
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
- }
- mHasVisibleTaskData.delete(task.key.id);
- }
- }
- }
-
- /**
- * Unloads any associated data from the currently visible tasks
- */
- private void unloadVisibleTaskData() {
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
- if (mHasVisibleTaskData.valueAt(i)) {
- TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
- Task task = taskView.getTask();
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
- }
- }
- mHasVisibleTaskData.clear();
- }
-
-
- protected abstract void onAllTasksRemoved();
-
- public void reset() {
- unloadVisibleTaskData();
- mRunningTaskId = -1;
- setCurrentPage(0);
- }
-
- /**
- * Reloads the view if anything in recents changed.
- */
- public void reloadIfNeeded() {
- if (!mModel.isLoadPlanValid(mLoadPlanId)) {
- mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
- }
- }
-
- /**
- * Ensures that the first task in the view represents {@param task} and reloads the view
- * if needed. This allows the swipe-up gesture to assume that the first tile always
- * corresponds to the correct task.
- * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
- * is called.
- * Also scrolls the view to this task
- */
- public void showTask(int runningTaskId) {
- boolean needsReload = false;
- if (getChildCount() == 0) {
- needsReload = true;
- // Add an empty view for now
- final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
- .inflate(R.layout.task, this, false);
- addView(taskView, 0);
- }
- mRunningTaskId = runningTaskId;
- setCurrentPage(0);
- if (!needsReload) {
- needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
- }
- if (needsReload) {
- mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
- } else {
- loadVisibleTaskData();
- }
- getPageAt(mCurrentPage).setAlpha(0);
- }
-
- public QuickScrubController getQuickScrubController() {
- return mQuickScrubController;
- }
-
- public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
- if (mFirstTaskIconScaledDown == isScaledDown) {
- return;
- }
- mFirstTaskIconScaledDown = isScaledDown;
- applyIconScale(animate);
- }
-
- private void applyIconScale(boolean animate) {
- float scale = mFirstTaskIconScaledDown ? 0 : 1;
- TaskView firstTask = (TaskView) getChildAt(0);
- if (firstTask != null) {
- if (animate) {
- firstTask.animateIconToScale(scale);
- } else {
- firstTask.setIconScale(scale);
- }
- }
- }
-
- public interface PageCallbacks {
-
- /**
- * Updates the page UI based on scroll params.
- */
- default void onPageScroll(ScrollState scrollState) {};
- }
-
- public static class ScrollState {
-
- /**
- * The progress from 0 to 1, where 0 is the center
- * of the screen and 1 is the edge of the screen.
- */
- public float linearInterpolation;
- }
-
- public PendingAnimation createTaskDismissAnimation(TaskView taskView, long duration) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
- throw new IllegalStateException("Another pending animation is still running");
- }
- AnimatorSet anim = new AnimatorSet();
- PendingAnimation pendingAnimation = new PendingAnimation(anim);
-
- int count = getChildCount();
- if (count == 0) {
- return pendingAnimation;
- }
-
- int[] oldScroll = new int[count];
- getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
-
- int[] newScroll = new int[count];
- getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
-
- int maxScrollDiff = 0;
- int lastPage = mIsRtl ? 0 : count - 1;
- if (getChildAt(lastPage) == taskView) {
- if (count > 1) {
- int secondLastPage = mIsRtl ? 1 : count - 2;
- maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
- }
- }
-
- boolean needsCurveUpdates = false;
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child == taskView) {
- addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
- addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
- duration, LINEAR, anim);
- } else {
- int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
- if (scrollDiff != 0) {
- addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
- duration, ACCEL, anim);
- needsCurveUpdates = true;
- }
- }
- }
-
- if (needsCurveUpdates) {
- ValueAnimator va = ValueAnimator.ofFloat(0, 1);
- va.addUpdateListener((a) -> updateCurveProperties());
- anim.play(va);
- }
-
- // Add a tiny bit of translation Z, so that it draws on top of other views
- taskView.setTranslationZ(0.1f);
-
- mPendingAnimation = pendingAnimation;
- mPendingAnimation.addEndListener((isSuccess) -> {
- if (isSuccess) {
- ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
- removeView(taskView);
- if (getChildCount() == 0) {
- onAllTasksRemoved();
- }
- }
- resetTaskVisuals();
- mPendingAnimation = null;
- });
- return pendingAnimation;
- }
-
- private static void addAnim(ObjectAnimator anim, long duration,
- TimeInterpolator interpolator, AnimatorSet set) {
- anim.setDuration(duration).setInterpolator(interpolator);
- set.play(anim);
- }
-}
diff --git a/res/drawable-v24/ic_setup_shadow.xml b/res/drawable-v24/ic_info_shadow.xml
similarity index 94%
rename from res/drawable-v24/ic_setup_shadow.xml
rename to res/drawable-v24/ic_info_shadow.xml
index 10aeee6..1fe2c46 100644
--- a/res/drawable-v24/ic_setup_shadow.xml
+++ b/res/drawable-v24/ic_info_shadow.xml
@@ -15,5 +15,5 @@
-->
<com.android.launcher3.graphics.ShadowDrawable
xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/ic_setting"
+ android:src="@drawable/ic_info_no_shadow"
android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/res/drawable/bg_all_apps_searchbox.xml b/res/drawable/all_apps_search_divider.xml
similarity index 78%
rename from res/drawable/bg_all_apps_searchbox.xml
rename to res/drawable/all_apps_search_divider.xml
index c324927..99905e4 100644
--- a/res/drawable/bg_all_apps_searchbox.xml
+++ b/res/drawable/all_apps_search_divider.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="?attr/popupColorPrimary" />
- <corners android:radius="2dp" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent" />
+ <size android:height="1dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_setting.xml b/res/drawable/ic_setting.xml
index 33cd6ce..08eba25 100644
--- a/res/drawable/ic_setting.xml
+++ b/res/drawable/ic_setting.xml
@@ -17,10 +17,9 @@
android:width="@dimen/options_menu_icon_size"
android:height="@dimen/options_menu_icon_size"
android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/textColorPrimary" >
+ android:viewportHeight="48.0">
<path
- android:fillColor="#FFFFFFFF"
+ android:fillColor="?android:attr/textColorPrimary"
android:pathData="M42.8,28.4l-3.88-2.9c0.06-0.5,0.08-1,0.08-1.52s-0.02-1.02-0.08-1.52l3.88-2.86c0.84-0.62,1.04-1.88,
0.48-2.82l-3.2-5.52c-0.56-0.96-1.76-1.4-2.72-1l-4.28,1.82c-0.96-0.74-2.02-1.36-3.14-1.84l-0.54-4.4C29.28,4.8,28.28,4,
27.18,4h-6.36c-1.1,0-2.1,0.8-2.22,1.84l-0.52,4.38c-1.14,0.48-2.2,1.1-3.16,1.84l-4.28-1.82c-0.96-0.4-2.16,0.04-2.72,
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 450d107..2ce6b8c 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -29,13 +29,20 @@
<include layout="@layout/all_apps_rv_layout" />
+ <include layout="@layout/all_apps_fast_scroller" />
+
<include layout="@layout/all_apps_floating_header" />
<!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
platform bug, which prevents using custom attributes in <include> tag -->
<include
android:id="@id/search_container_all_apps"
- layout="@layout/search_container_all_apps"/>
+ layout="?android:attr/keyboardLayout"/>
- <include layout="@layout/all_apps_fast_scroller" />
+ <View
+ android:id="@+id/nav_bar_bg"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_alignParentBottom="true"
+ android:background="?attr/allAppsNavBarScrimColor" />
</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
index c353b36..3c19f8c 100644
--- a/res/layout/all_apps_rv_layout.xml
+++ b/res/layout/all_apps_rv_layout.xml
@@ -22,4 +22,5 @@
android:layout_below="@id/search_container_all_apps"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
- android:focusable="true" />
+ android:focusable="true"
+ android:overScrollMode="never" />
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 4a2ad42..4a3db1f 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -35,6 +35,7 @@
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="sans-serif"
launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true"
launcher:iconDisplay="shortcut_popup"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
diff --git a/res/layout/drop_target_bar.xml b/res/layout/drop_target_bar.xml
index 2f21c60..d376bcf 100644
--- a/res/layout/drop_target_bar.xml
+++ b/res/layout/drop_target_bar.xml
@@ -32,8 +32,18 @@
android:gravity="center"
android:text="@string/remove_drop_target_label" />
+ <!-- App Info -->
+ <com.android.launcher3.InfoDropTarget
+ android:id="@+id/info_target_text"
+ style="@style/DropTargetButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:text="@string/app_info_drop_target_label" />
+
<!-- Uninstall target -->
- <com.android.launcher3.SecondaryDropTarget
+ <com.android.launcher3.UninstallDropTarget
android:id="@+id/uninstall_target_text"
style="@style/DropTargetButton"
android:layout_width="wrap_content"
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index d07ff81..314359b 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -44,6 +44,11 @@
layout="@layout/overview_panel"
android:visibility="gone" />
+ <com.android.launcher3.views.AllAppsScrim
+ android:id="@+id/all_apps_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<!-- DO NOT CHANGE THE ID -->
<include
android:id="@+id/hotseat"
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index bdd5d23..c795b81 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,7 +14,61 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<Space
+<com.android.launcher3.uioverrides.OverviewPanel
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="0dp" />
\ No newline at end of file
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:gravity="top"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/wallpaper_button"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:drawablePadding="4dp"
+ android:drawableTop="@drawable/ic_wallpaper"
+ android:drawableTint="?attr/workspaceTextColor"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:stateListAnimator="@animator/overview_button_anim"
+ android:text="@string/wallpaper_button_text"
+ android:textAllCaps="true"
+ android:textColor="?attr/workspaceTextColor"
+ android:textSize="12sp" />
+
+ <TextView
+ android:id="@+id/widget_button"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:drawablePadding="4dp"
+ android:drawableTop="@drawable/ic_widget"
+ android:drawableTint="?attr/workspaceTextColor"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:stateListAnimator="@animator/overview_button_anim"
+ android:text="@string/widget_button_text"
+ android:textAllCaps="true"
+ android:textColor="?attr/workspaceTextColor"
+ android:textSize="12sp" />
+
+ <TextView
+ android:id="@+id/settings_button"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:drawablePadding="4dp"
+ android:drawableTop="@drawable/ic_setting"
+ android:drawableTint="?attr/workspaceTextColor"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:stateListAnimator="@animator/overview_button_anim"
+ android:text="@string/settings_button_text"
+ android:textAllCaps="true"
+ android:textColor="?attr/workspaceTextColor"
+ android:textSize="12sp" />
+
+</com.android.launcher3.uioverrides.OverviewPanel>
\ No newline at end of file
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index 14d7b53..fc07002 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -17,23 +17,53 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
- android:layout_height="@dimen/all_apps_search_bar_field_height"
- android:layout_centerHorizontal="true"
- android:layout_gravity="top|center_horizontal"
- android:layout_marginTop="8dp"
- android:background="@drawable/bg_all_apps_searchbox"
- android:elevation="1dp"
- android:focusableInTouchMode="true"
- android:gravity="center"
- android:hint="@string/all_apps_search_bar_hint"
- android:imeOptions="actionSearch|flagNoExtractUi"
- android:inputType="text|textNoSuggestions|textCapWords"
- android:maxLines="1"
- android:padding="8dp"
- android:saveEnabled="false"
- android:scrollHorizontally="true"
- android:singleLine="true"
- android:textColor="?android:attr/textColorSecondary"
- android:textColorHint="@drawable/all_apps_search_hint"
- android:textSize="16sp"
- android:translationY="24dp" />
\ No newline at end of file
+ android:layout_height="@dimen/all_apps_search_bar_height"
+ android:layout_gravity="center|top"
+ android:layout_marginBottom="-8dp"
+ android:gravity="center|bottom"
+ android:paddingLeft="@dimen/dynamic_grid_edge_margin"
+ android:paddingRight="@dimen/dynamic_grid_edge_margin"
+ android:saveEnabled="false" >
+
+ <!--
+ Note: The following relation should always be true so that the shadows are aligned properly
+ search_box_input.layout_marginBottom
+ == search_divider.layout_marginBottom
+ == - (search_container.layout_marginBottom)
+ >= 5dp
+ -->
+ <com.android.launcher3.ExtendedEditText
+ android:id="@+id/search_box_input"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="8dp"
+ android:background="@android:color/transparent"
+ android:focusableInTouchMode="true"
+ android:gravity="center"
+ android:hint="@string/all_apps_search_bar_hint"
+ android:imeOptions="actionSearch|flagNoExtractUi"
+ android:inputType="text|textNoSuggestions|textCapWords"
+ android:maxLines="1"
+ android:saveEnabled="false"
+ android:scrollHorizontally="true"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="@drawable/all_apps_search_hint"
+ android:textSize="16sp" />
+
+ <!-- This needs to be a container with a view, to simulate a scrolling effect for the header.
+ We translate the header down, and its content up by the same amount, so that it gets
+ clipped by the parent, making it look like the divider was scrolled out of the view. -->
+ <FrameLayout
+ android:id="@+id/search_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="8dp" >
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@drawable/all_apps_search_divider"/>
+ </FrameLayout>
+</com.android.launcher3.allapps.search.AppsSearchContainerLayout>
\ No newline at end of file
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 04f3d02..1888e22 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -34,6 +34,7 @@
android:fontFamily="sans-serif"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true"
android:focusable="false" />
<View
diff --git a/res/drawable-v24/ic_setup_shadow.xml b/res/layout/widgets_bottom_sheet_scrim.xml
similarity index 67%
copy from res/drawable-v24/ic_setup_shadow.xml
copy to res/layout/widgets_bottom_sheet_scrim.xml
index 10aeee6..6c6626c 100644
--- a/res/drawable-v24/ic_setup_shadow.xml
+++ b/res/layout/widgets_bottom_sheet_scrim.xml
@@ -5,7 +5,7 @@
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
+ 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,
@@ -13,7 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.graphics.ShadowDrawable
+
+<com.android.launcher3.graphics.GradientView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/ic_setting"
- android:elevation="@dimen/drop_target_shadow_elevation" />
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/gradient_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:layout_ignoreInsets="true" />
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index f507a88..20eca9f 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -20,6 +20,11 @@
android:orientation="vertical"
android:theme="?attr/widgetsTheme" >
+ <com.android.launcher3.graphics.GradientView
+ android:id="@+id/gradient_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<com.android.launcher3.views.TopRoundedCornerView
android:id="@+id/container"
android:layout_width="match_parent"
@@ -31,22 +36,29 @@
android:id="@+id/widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ />
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
style="@style/FastScrollerPopup"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
+ android:layout_gravity="top|end"
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
<com.android.launcher3.views.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="@dimen/fastscroll_width"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
+ android:layout_gravity="end"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+ <View
+ android:id="@+id/nav_bar_bg"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="bottom"
+ android:background="?attr/allAppsNavBarScrimColor"
+ android:focusable="false" />
</com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 91baf7a..4cd03ce 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -42,6 +42,7 @@
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textAlignment="viewStart"
+ launcher:deferShadowGeneration="true"
launcher:iconDisplay="widget_section"
launcher:iconSizeOverride="@dimen/widget_section_icon_size"
launcher:layoutHorizontal="true" />
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 379e9d0..21ff55e 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,7 +17,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:focusable="true"
android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index cfa1492..1ff9a2d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Werk"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Werkprofiel"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Kry werkprogramme hier"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Elke werkprogram het \'n oranje kenteken en word deur jou organisasie veilig gehou. Skuif programme na jou Tuisskerm vir makliker toegang."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Bestuur deur jou organisasie"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Kennisgewings en programme is af"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 07fe5ef..67c26e7 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ሥራ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"የሥራ መገለጫ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"የስራ መተግበሪያዎችን እዚህ ያግኙ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"እያንዳንዱ የሥራ መተግበሪያ ብርቱካናማ ባጅ አለው እና በእርስዎ ድርጅት በኩል ደህንነቱ ተጠብቋል። ለቀለለ መዳረሻ መተግበሪያዎችን ወደ የእርስዎ መነሻ ማያ ገጽ ያንቀሳቅሱ።"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"በእርስዎ ድርጅት የሚተዳደር"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ማሳወቂያዎች እና መተግበሪያዎች ጠፍተዋል"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 95508e1..1b1d7ca 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -141,8 +141,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"للعمل"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"الملف الشخصي للعمل"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"البحث عن تطبيقات العمل هنا"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"يحتوي كل تطبيق للعمل على شارة برتقالية اللون ويظل تحت حماية مؤسستك. يمكنك نقل التطبيقات إلى شاشتك الرئيسية لتسهيل الوصول."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"ملف شخصي للعمل تديره مؤسستك"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"الإشعارات والتطبيقات متوقفة."</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index d4afc53..00f71d0 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"İş"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"İş profili"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Burada iş tətbiqləri axtarın"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Hər bir iş tətbiqində təşkilat tərəfindən qorunduğunu göstərən narıncı nişan var. Tətbiqləri daha asan giriş üçün Əsas Səhifə Ekranına köçürün."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Təşkilatınız tərəfindən idarə olunur"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Bildiriş və tətbiqlər deaktivdir"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 4b577ba..0ee8b3a 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Poslovne"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil za Work"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Pronađite poslovne aplikacije ovde"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Svaka poslovna aplikacija ima narandžastu značku i štiti je vaša organizacija. Premestite aplikacije na početni ekran da biste im lakše pristupali."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Ovim upravlja organizacija"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Obaveštenja i aplikacije su isključeni"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 155ac3c..2f5eac7 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Праца"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Працоўны профіль"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Знайдзіце працоўныя праграмы тут"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Кожная працоўная праграма мае аранжавы значок і знаходзіцца пад аховай вашай арганізацыі. Для больш простага доступу перамясціце праграмы на свой Галоўны экран."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Пад кіраваннем вашай арганізацыі"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Апавяшчэнні і праграмы выключаны"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 113c6d1..94b7f13 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Служебни"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Служебен потребителски профил"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Тук можете да намерите служебните приложения"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Всяко служебно приложение има оранжева значка и организацията ви се грижи за сигурността му. За по-лесен достъп преместете приложенията на началния си екран."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Управлява се от организацията ви"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Известията и приложенията са изключени"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index ed57612..d9c6033 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"অফিস"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"অফিসের প্রোফাইল"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"এখানে কাজের অ্যাপ্সগুলি খুঁজুন"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"প্রতিটি কাজের অ্যাপে একটি করে কমলা ব্যাজ রয়েছে এবং অ্যাপগুলি আপনার প্রতিষ্ঠানের দ্বারা সুরক্ষিত। সহজে অ্যাক্সেস করার জন্য অ্যাপগুলি হোম স্ক্রিনে রাখুন।"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"আপনার প্রতিষ্ঠানের দ্বারা পরিচালিত"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"বিজ্ঞপ্তি এবং অ্যাপ বন্ধ আছে"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index a96abde..84b2b78 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -41,7 +41,7 @@
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži više aplikacija"</string>
<string name="notifications_header" msgid="1404149926117359025">"Obavještenja"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom ekranu nema više prostora."</string>
- <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora u ladici Omiljeno"</string>
+ <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora u ladici Favoriti"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Spisak aplikacija"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"Početna"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
@@ -112,7 +112,7 @@
<string name="action_move" msgid="4339390619886385032">"Premjesti stavku"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"Pomjeri stavku u red <xliff:g id="NUMBER_0">%1$s</xliff:g> kolonu <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
<string name="move_to_position" msgid="6750008980455459790">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
- <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g> među omiljenim"</string>
+ <string name="move_to_hotseat_position" msgid="6295412897075147808">"Pomjeri stavku na poziciju <xliff:g id="NUMBER">%1$s</xliff:g> među favoritima"</string>
<string name="item_moved" msgid="4606538322571412879">"Stavka je premještena"</string>
<string name="add_to_folder" msgid="9040534766770853243">"Dodaj u folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="add_to_folder_with_app" msgid="4534929978967147231">"Dodaj u folder sa aplikacijom <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Poslovne"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Radni profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Pronađite poslovne aplikacije ovdje"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Svaka poslovna aplikacija ima narandžastu značku i osigurava je vaša organizacija. Premjestite aplikacije na Početni ekran, radi lakšeg pristupa."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Upravlja vaša organizacija"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notifikacije i aplikacije su isključene"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b9ba069..1ca68eb 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Feina"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil professional"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Cerca aplicacions per a la feina aquí"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Totes les aplicacions per a la feina tenen una insígnia taronja que indica que estan protegides per la teva organització. Mou les aplicacions a la pantalla d\'inici per poder-hi accedir més fàcilment."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gestionat per la teva organització"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Les notificacions i les aplicacions estan desactivades"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 608d297..e5fde42 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovní"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovní profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Zde naleznete pracovní aplikace"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Každý pracovní profil má oranžový odznak a je zabezpečen vaší organizací. Aplikace si můžete pro jednoduchost přesunout na plochu."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Spravováno vaší organizací"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Oznámení a aplikace jsou vypnuty"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 0aaedca..97d8a26 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Arbejde"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbejdsprofil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find arbejdsapps her"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Alle arbejdsapps har et orange badge og beskyttes af din organisation. Flyt apps til din startskærm, så du nemmere kan få adgang til dem."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Administreret af din organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Underretninger og apps er slået fra"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 5cc1451..96acfba 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Geschäftlich"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbeitsprofil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Hier findest du Apps für die Arbeit"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Jede App für die Arbeit hat ein orangefarbenes Logo. Deine Organisation kümmert sich um den entsprechenden Schutz. Damit du leichter auf Apps zugreifen kannst, verschiebe sie auf deinen Startbildschirm."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Wird von deiner Organisation verwaltet"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Benachrichtigungen und Apps sind deaktiviert"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 21c9706..9328c5f 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Εργασίας"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Προφίλ εργασίας"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Βρείτε όλες τις εφαρμογές εργασίας εδώ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Κάθε εφαρμογή εργασίας φέρει ένα πορτοκαλί σήμα και διατηρείται ασφαλής από τον οργανισμό σας. Μετακινήστε τις εφαρμογές εργασίας στην Αρχική οθόνη, για να έχετε πιο εύκολη πρόσβαση."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Διαχειριζόμενο από τον οργανισμό σας"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Οι ειδοποιήσεις και οι εφαρμογές είναι απενεργοποιημένες"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index c84efc7..d7ab7d2 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Each work app has an orange badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index c84efc7..d7ab7d2 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Each work app has an orange badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index c84efc7..d7ab7d2 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Each work app has an orange badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 23bf97d..11cfa2a 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Laborales"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Apps de trabajo"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Cada app de trabajo tiene una insignia naranja y está protegida por tu organización. Mueve las apps a la pantalla principal para acceder a ellas con mayor facilidad."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Administrado por tu organización"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Las notificaciones y las apps están desactivadas"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 0fb401a..39da94c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Trabajo"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Aplicaciones de trabajo"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Cada aplicación de trabajo tiene una insignia naranja y está protegida por tu organización. Mueve las aplicaciones a la pantalla de inicio para acceder a ellas más fácilmente."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Administrada por tu organización"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Las notificaciones y las aplicaciones están desactivadas"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 555f997..68a0429 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Töö"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Tööprofiil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Töörakendused leiate siit"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Igal töörakendusel on oranž märk ja teie organisatsioon tagab selle turvalisuse. Teisaldage rakendused avaekraanile, et neile oleks lihtsam juurde pääseda."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Haldab teie organisatsioon"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Märguanded ja rakendused on välja lülitatud"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 80d705d..27a56dd 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Lanekoak"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Laneko profila"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Hemen dituzu laneko aplikazioak"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Laneko aplikazio bakoitzak bereizgarri laranja bat dauka eta erakundeak babesten du. Aplikazioak errazago atzitzeko, eraman itzazu hasierako pantailara."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Erakundeak kudeatzen du"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Jakinarazpenak eta aplikazioak desaktibatuta daude"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 5528119..128af90 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"محل کار"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"نمایه کاری"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"اینجا برنامههای کاری را پیدا کنید"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"همه برنامههای کاری نشان نارنجیرنگی دارند و توسط سازمان شما امن نگه داشته میشود. برنامههای کاری را برای دسترسی آسانتر به صفحه اصلی انتقال دهید."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"توسط سازمانتان مدیریت میشود"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"اعلانها و برنامهها خاموش هستند"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 9560b66..f5b0817 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Työsovellukset"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Työprofiili"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Etsi työsovelluksia tästä"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Kaikissa työsovelluksissa on oranssi merkki ja ne ovat organisaatiosi suojaamia. Voit siirtää työsovelluksia aloitusnäytölle käytön helpottamiseksi."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Organisaatiosi hallinnoima"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Ilmoitukset ja sovellukset ovat poissa käytöstä"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index c696180..4093535 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Travail"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Trouvez ici des applications professionnelles"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Chaque application professionnelle comporte un badge orange, ce qui signifie qu\'elle est sécurisée par votre organisation. Vous pouvez déplacer vos applications vers l\'écran d\'accueil afin d\'y accéder plus facilement."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Géré par votre organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Les notifications et les applications sont désactivées"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e0997fc..520ad78 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Professionnelles"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Retrouvez ici vos applications professionnelles"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Les applications professionnelles sont accompagnées d\'un badge orange et sont sécurisées par votre organisation. Vous pouvez les déplacer vers votre écran d\'accueil pour y accéder plus facilement."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Géré par votre organisation"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Les notifications et les applications sont désactivées"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 219b76e..9a842b1 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Traballo"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de traballo"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Buscar aplicacións do traballo aquí"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"As aplicacións do traballo teñen unha insignia laranxa e están protexidas pola túa organización. Traslada as aplicacións á pantalla de inicio para acceder a elas de forma máis fácil."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Perfil xestionado pola túa organización"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"As notificacións e as aplicacións están desactivadas"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 68b4edd..e030659 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"કાર્યાલયની ઍપ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"કાર્યાલયની પ્રોફાઇલ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"કાર્ય ઍપને અહીંથી મેળવો"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"દરેક કાર્ય ઍપ પાસે એક નારંગી બૅજ હોય છે અને તમારી સંસ્થા દ્વારા તેને સુરક્ષિત રાખવામાં આવે છે. વધુ સરળ ઍક્સેસ માટે ઍપને તમારી હોમ સ્ક્રીન પર ખસેડો."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"તમારી સંસ્થા દ્વારા મેનેજ કરેલ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"નોટિફિકેશન અને ઍપ બંધ છે"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 15a4deb..aa1e8dd 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"काम से जुड़े ऐप"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफ़ाइल"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"काम से जुड़े सभी ऐप्लिकेशन यहां पाएं"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"काम से जुड़े हर ऐप्लिकेशन पर एक नारंगी रंग का बैज (निशान) होता है जिसकी सुरक्षा आपका संगठन करता है. आसानी से इस्तेमाल करने के लिए ऐप्लिकेशन को अपनी होम स्क्रीन पर ले जाएं."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"आपका संगठन प्रबंधित कर रहा है"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"सूचनाएं और ऐप्लिकेशन बंद हैं"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d28c108..ec3c6b2 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Posao"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Radni profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Ovdje možete pronaći radne aplikacije"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Svaka radna aplikacija ima narančastu značku i štiti je vaša organizacija. Premjestite aplikacije na početni zaslon radi lakšeg pristupa."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Pod upravljanjem vaše organizacije"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Obavijesti i aplikacije isključeni su"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index f690196..697153a 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Munkahelyi"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Munkaprofil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Itt kereshet munkahelyi alkalmazásokat"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Az egyes munkahelyi alkalmazásokon narancs jelvény található, és biztonságukról az Ön szervezete gondoskodik. A könnyebb hozzáférés érdekében helyezze át az alkalmazásokat a kezdőképernyőre."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Az Ön szervezete kezeli"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Az értesítések és az alkalmazások ki vannak kapcsolva"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index e233a44..02a561f 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Աշխատանքային"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Աշխատանքային պրոֆիլ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Գտեք աշխատանքային հավելվածներ այստեղ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Աշխատանքային հավելվածները նշված են նարնջագույն նշանով, նման հավելվածների անվտանգությունը ապահովում է ձեր կազմակերպությունը։ Հարմարության համար աշխատանքային հավելվածները կարող եք տեղափոխել հիմնական էկրան։"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Կառավարվում է ձեր կազմակերպության կողմից"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Ծանուցումներն ու հավելվածներն անջատված են"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 89e57e7..d115c73 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Kantor"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil kerja"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Temukan aplikasi kerja di sini"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Setiap aplikasi kerja memiliki badge oranye dan dibuat tetap aman oleh organisasi. Pindahkan aplikasi ke Layar utama untuk memudahkan akses."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Dikelola oleh organisasi"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notifikasi dan aplikasi nonaktif"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 1e89142..ba9a47d 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Vinna"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Vinnusnið"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Hér finnurðu vinnuforrit"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Öll vinnuforrit eru með appelsínugulu merki og fyrirtækið þitt tryggir öryggi þeirra. Færðu forrit yfir á heimaskjáinn fyrir auðveldari aðgang að þeim."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Stjórnað af fyrirtækinu þínu"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Slökkt er á tilkynningum og forritum"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8584629..6c98d3e 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Lavoro"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profilo di lavoro"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Qui puoi trovare le tue app di lavoro"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Ogni app di lavoro è contrassegnata da un badge arancione e viene tenuta al sicuro dalla tua organizzazione. Sposta le app nella schermata Home per accedervi più facilmente."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gestito dalla tua organizzazione"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Le notifiche e le app non sono attive"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 961bd01..43c8520 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ניתן למצוא כאן את אפליקציות העבודה"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"לכל אפליקציית עבודה יש תג כתום ואבטחתה מטופלת בידי הארגון. אפשר להעביר אפליקציות אל מסך דף הבית כדי להקל את הגישה אליהן."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"מנוהל בידי הארגון"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"הודעות ואפליקציות כבויות"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 75db7d6..36f3c5e 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"仕事用"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"仕事用プロファイル"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ここには仕事用アプリが表示されます"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"仕事用アプリにはオレンジのバッジが表示され、組織によって安全に保護されています。仕事用アプリをホーム画面に移動すると、簡単にアクセスできます。"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"組織によって管理されています"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"通知とアプリは OFF です"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 5f3d47f..b6d7877 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"სამსახური"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"სამსახურის პროფილი"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"აქ თავმოყრილია სამსახურის აპები"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"სამსახურის აპები მონიშნულია სტაფილოსფერი ბეჯით, რაც ნიშნავს, რომ მათ უსაფრთხოებას თქვენი ორგანიზაცია უზრუნველყოფს. მარტივი წვდომისთვის, შეგიძლიათ სამსახურის აპები მთავარი ეკრანზე გადაიტანოთ."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"იმართება თქვენი ორგანიზაციის მიერ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"შეტყობინებები და აპები გამორთულია"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 12da505..6c4deec 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Жұмыс"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Жұмыс профилі"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Жұмыс қолданбалары осы жерде берілген"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Әрбір жұмыс қолданбасында қызғылт сары танымбелгі бар. Ол оның қауіпсіздігі ұйым арқылы қамтамасыз етілетінін білдіреді. Жұмыс қолданбаларына оңай кіру үшін, оларды Негізгі экранға жылжытуға болады."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Ұйым арқылы басқарылады"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Хабарландырулар мен қолданбалар өшірулі"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 9704194..d588bc1 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ការងារ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"កម្រងព័ត៌មានការងារ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ស្វែងរកកម្មវិធីការងារនៅទីនេះ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"កម្មវិធីការងារនីមួយៗមានស្លាកពណ៌ទឹកក្រូច និងត្រូវបានរក្សាទុកយ៉ាងមានសុវត្ថិភាពដោយស្ថាប័នរបស់អ្នក។ សូមផ្លាស់ទីកម្មវិធីទៅកាន់អេក្រង់ដើមរបស់អ្នក ដើម្បីងាយស្រួលចូលប្រើជាងមុន។"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"គ្រប់គ្រងដោយស្ថាប័នរបស់អ្នក"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ការជូនដំណឹង និងកម្មវិធីត្រូវបានបិទ"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1746cbb..a8f9784 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ಕೆಲಸ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಇಲ್ಲಿ ಹುಡುಕಿ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ಕೆಲಸದ ಪ್ರತಿ ಅಪ್ಲಿಕೇಶನ್ ಕಿತ್ತಳೆ ಬ್ಯಾಡ್ಜ್ ಹೊಂದಿದೆ ಮತ್ತು ನಿಮ್ಮ ಸಂಸ್ಥೆಯಿಂದ ಸುರಕ್ಷಿತವಾಗಿ ಇರಿಸಲಾಗುತ್ತದೆ. ಸುಲಭ ಪ್ರವೇಶಕ್ಕಾಗಿ ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್ಗೆ ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಸರಿಸಿ."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ಮೂಲಕ ನಿರ್ವಹಿಸಲಾಗಿದೆ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ಅಧಿಸೂಚನೆಗಳು ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್ಗಳು ಆಫ್ ಆಗಿವೆ"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 580297f..0ebbbe7 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"직장"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"직장 프로필"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"여기에서 업무용 앱 찾기"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"각 업무용 앱에는 주황색 배지가 있으며 업무용 앱은 조직에서 안전하게 보호됩니다. 앱을 홈 화면으로 이동하여 더 간편하게 사용하세요."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"조직에서 관리"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"알림 및 앱 사용 중지됨"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index e6493db..05652fc 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Жумуш колдонмолору"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Жумуш профили"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Жумуш колдонмолорун бул жерден таап алыңыз"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Ар бир жумуш колдонмосунун кызгылт сары бейджиги бар жана ал уюмуңуз тарабынан коопсуз сакталат. Колдонмолорго тез өтүү үчүн аларды Башкы экранга кошуп алыңыз."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Уюмуңуз тарабынан башкарылат"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Билдирүүлөр жана колдонмолор өчүрүлгөн"</string>
</resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 6cf23ad..40ffffe 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -25,6 +25,7 @@
<dimen name="fastscroll_popup_text_size">24dp</dimen>
<!-- Dynamic grid -->
+ <dimen name="dynamic_grid_overview_bar_item_width">120dp</dimen>
<dimen name="dynamic_grid_min_page_indicator_size">48dp</dimen>
<dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index c0c9bb0..e446e7c 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ວຽກ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ຊອກຫາແອັບວຽກຢູ່ບ່ອນນີ້"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ແຕ່ລະແອັບວຽກຈະມີປ້າຍສີສົ້ມ ແລະ ຖືກຈັດເກັບໄວ້ຢ່າງປອດໄພໂດຍອົງກອນຂອງທ່ານ. ທ່ານສາມາດຍ້າຍແອັບໄປໃສ່ໜ້າຈໍຫຼັກເພື່ອໃຫ້ເຂົ້າໃຊ້ງ່າຍຂຶ້ນໄດ້."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"ຈັດການໂດຍອົງກອນຂອງທ່ານ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ການແຈ້ງເຕືອນ ແລະ ແອັບຖືກປິດໄວ້"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 3e985cb..2e37e98 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Darbo"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Darbo profilis"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Darbo programas rasite čia"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Kiekvienai darbo programai priskirtas oranžinis ženklelis, o tokių programų sauga rūpinasi jūsų organizacija. Perkelkite darbo programas į pagrindinį ekraną, kad galėtumėte lengviau jas pasiekti."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Tvarko jūsų organizacija"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Programos ir pranešimai išjungti"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 6a9d157..3b96ffa 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Darba lietotnes"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Darba profils"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Meklējiet darba lietotnes šeit"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Katrai darba lietotnei ir oranža emblēma, un jūsu organizācija aizsargā šīs lietotnes. Ērtākai piekļuvei pārvietojiet darba lietotnes uz sākuma ekrānu."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Pārvalda jūsu organizācija"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Paziņojumi un lietotnes ir izslēgtas"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 01408dd..3a0fc2b 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"За работа"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Работен профил"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Најдете апликации за работа тука"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Секоја апликација за работа има портокалова значка и е обезбедена од вашата организација. За полесен пристап, апликациите за работа преместете ги на почетниот екран."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Управувано од вашата организација"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Известувањата и апликациите се исклучени"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 6e6340b..84f769b 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ജോലി"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ഔദ്യോഗിക ആപ്പുകൾ ഇവിടെ കണ്ടെത്തുക"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"എല്ലാ ഔദ്യോഗിക ആപ്പിനും ഓറഞ്ച് നിറത്തിലുള്ള ഒരു ബാഡ്ജ് ഉണ്ട്, നിങ്ങളുടെ സ്ഥാപനം അത് സുരക്ഷിതമായി സൂക്ഷിക്കുന്നു. എളുപ്പത്തിൽ ആക്സസ് ചെയ്യാൻ ആപ്പുകളെ ഹോം സ്ക്രീനിലേക്ക് നീക്കുക."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"നിങ്ങളുടെ സ്ഥാപനം നിയന്ത്രിക്കുന്നത്"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"അറിയിപ്പുകളും ആപ്പുകളും ഓഫാണ്"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 7119ccf..5e91a46 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Ажил"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Ажлын профайл"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Ажлын аппыг эндээс олно уу"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Ажлын апп тус бүр улбар шар тэмдэгтэй ба эдгээрийг танай байгууллагаас аюулгүй байлгадаг. Аппуудад хялбараар хандахын тулд тэдгээрийг Үндсэн Нүүрэнд зөөнө үү."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Танай байгууллагаас удирддаг"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Мэдэгдэл, апп унтраалттай байна"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index fd5d999..94b75d7 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"कार्यालय"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"कामाची अॅप्स येथे मिळवा"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"प्रत्येक कार्य अॅपमध्ये नारिंगी बॅज असतो आणि तो तुमच्या संस्थेकडून सुरक्षित ठेवला जातो. अधिक सहज अॅक्सेससाठी अॅप्स तुमच्या होम स्क्रीनवर हलवा."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"तुमच्या संस्थेकडून व्यवस्थापित"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अॅप्स बंद आहेत"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 1fe38a6..6e19421 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Kerja"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil kerja"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Temui apl kerja di sini"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Setiap apl kerja terdapat lencana berwarna oren dan dilindungi oleh organisasi anda. Alihkan apl ke Skrin Utama untuk akses yang lebh mudah."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Diurus oleh organisasi anda"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Pemberitahuan dan apl dimatikan"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index c004318..3cf9208 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"အလုပ်"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"အလုပ်ပရိုဖိုင်"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"အလုပ်အက်ပ်များကို ဤနေရာတွင်ရှာဖွေပါ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"အလုပ်အက်ပ်တိုင်းတွင် လိမ္မော်ရောင်တံဆိပ် တစ်ခုစီရှိပြီး သင်၏ အဖွဲ့အစည်းက လုံခြုံအောင် ထားရှိပါသည်။ အသုံးပြုရ ပိုမိုလွယ်ကူစေရန် အက်ပ်များကို သင်၏ ပင်မမျက်နှာပြင်သို့ ရွှေ့ပါ။"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"သင်၏ အဖွဲ့အစည်းက စီမံခန့်ခွဲထားပါသည်"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"အကြောင်းကြားချက်များနှင့် အက်ပ်များကို ပိတ်ထားသည်"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 7a36062..2bdd6ce 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Jobb"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Finn jobbapper her"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Alle jobbapper har et oransje merke og sikres av organisasjonen din. Flytt apper til startskjermen for å gjøre det enklere å finne dem."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Administreres av organisasjonen din"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Varsler og apper er slått av"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 602b162..d15e8b2 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"कार्यसम्बन्धी"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"कार्यसम्बन्धी अनुप्रयोगहरू यहाँ प्राप्त गर्नुहोस्"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"कार्यसम्बन्धी प्रत्येक अनुप्रयोगमा एउटा सुन्तला रङको ब्याज छ र यसलाई तपाईंको संगठनद्वारा सुरक्षित राखिएको छ । अझ सजिलो गरी पहुँच राख्नका लागि कार्यसम्बन्धी अनुप्रयोगहरूलाई तपाईंको गृहस्क्रिनमा सार्नुहोस्।"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"तपाईंको सङ्गठनले व्यवस्थापन गरेको"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"सूचना र अनुप्रयोगहरू निष्क्रिय छन्"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index a686838..38c24a5 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Werk"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Werkprofiel"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Zoek hier naar werk-apps"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Elke werk-app heeft een oranje badge en wordt beveiligd door je organisatie. Verplaats apps naar je startscherm om gemakkelijker toegang te krijgen."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Beheerd door je organisatie"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Meldingen en apps zijn uitgeschakeld"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index f81c4a2..6b6c929 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ਕਾਰਜ-ਸਥਾਨ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ਕਾਰਜ-ਸਥਾਨ ਐਪਾਂ ਇੱਥੇ ਲੱਭੋ"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ਹਰੇਕ ਕਾਰਜ-ਸਥਾਨ ਐਪ ਦਾ ਇੱਕ ਸੰਤਰੀ ਬੈਜ ਹੁੰਦਾ ਹੈ ਅਤੇ ਉਸਨੂੰ ਤੁਹਾਡੀ ਸੰਸਥਾ ਰਾਹੀਂ ਸੁਰੱਖਿਅਤ ਰੱਖਿਆ ਜਾਂਦਾ ਹੈ। ਵਧੇਰੇ ਆਸਾਨ ਪਹੁੰਚ ਲਈ ਐਪਾਂ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲਿਜਾਓ।"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ਸੂਚਨਾਵਾਂ ਅਤੇ ਐਪਾਂ ਬੰਦ ਹਨ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 77aeee2..c90ba1e 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Praca"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil służbowy"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Aplikacje do pracy"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Każda aplikacja do pracy ma pomarańczową plakietkę, a o jej bezpieczeństwo dba Twoja organizacja. Aplikacje można przenieść na ekran główny, by były łatwiej dostępne."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Profil zarządzany przez Twoją organizację"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Powiadomienia i aplikacje są wyłączone"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index cd473fa..c370a8e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Encontrar as aplicações de trabalho aqui"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Cada aplicação de trabalho apresenta um emblema laranja, pelo que a sua entidade a mantém em segurança. Pode mover as aplicações para o ecrã principal para facilitar o acesso."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gerido pela sua entidade"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"As notificações e as aplicações estão desativadas."</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 48e37ef..0686ae7 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Comerciais"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Localizar apps de trabalho aqui"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Cada app de trabalho tem um selo laranja e é mantido em segurança pela sua organização. Mova os apps para sua tela inicial para facilitar o acesso."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gerenciados pela sua organização"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"As notificações e os apps estão desativados"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 495ba4c..651b264 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Profesionale"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil de serviciu"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Găsiți aplicații de serviciu aici"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Fiecare aplicație de serviciu are o insignă portocalie și este păstrată în siguranță de organizația dvs. Mutați aplicațiile de serviciu pe ecranul de pornire pentru acces mai ușor."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gestionat de organizația dvs."</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Notificările și aplicațiile sunt dezactivate"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 10490f3..9f43c56 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -67,7 +67,7 @@
<item quantity="other">В приложении \"<xliff:g id="APP_NAME_2">%1$s</xliff:g>\" <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> уведомления</item>
</plurals>
<string name="default_scroll_format" msgid="7475544710230993317">"Стр. %1$d из %2$d"</string>
- <string name="workspace_scroll_format" msgid="8458889198184077399">"Главный экран %1$d из %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Главные экран %1$d из %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Новый экран"</string>
<string name="folder_opened" msgid="94695026776264709">"Папка открыта, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
<string name="folder_tap_to_close" msgid="4625795376335528256">"Нажмите, чтобы закрыть папку"</string>
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Рабочие"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Рабочий профиль"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Приложения для работы"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Рабочие приложения отмечены оранжевым значком, который подтверждает, что ваша организация гарантирует их безопасность. Для удобства перенесите эти приложения на главный экран."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Управляется вашей организацией"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Уведомления и приложения отключены."</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 5024fec..bbcc257 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"කාර්යාලය"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"කාර්යාල පැතිකඩ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"මෙහි කාර්යාල යෙදුම් සොයා ගන්න"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"සෑම කාර්යාල යෙදුමකම තැඹිලි ලාංඡනයක් ඇත ඇති අතර එය ඔබේ සංවිධානය විසින් සුරක්ෂිතව තබා ගනී. වඩා පහසුවෙන් පිවිසීමට යෙදුම් ඔබගේ මුල් පිටු තිරය වෙත ගෙන යන්න."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"ඔබේ සංවිධානය විසින් කළමනාකරණය කරනු ලැබේ"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"දැනුම්දීම් සහ යෙදුම් ක්රියාවිරහිතයි"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 58baac4..a3bc4ec 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovné"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovný profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Tu nájdete pracovné aplikácie"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Všetky pracovné aplikácie majú oranžový štítok a sú bezpečne uchovávané vašou organizáciou. Ak chcete mať k aplikáciám ľahší prístup, presuňte ich na plochu."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Spravované vašou organizáciou"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Upozornenia a aplikácie sú vypnuté"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 758c5b2..337cb2d 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Služba"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Delovni profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Tukaj poiščite delovne aplikacije"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Vsaka delovna aplikacija ima oranžno značko. Za varnost teh aplikacij skrbi vaša organizacija. Za preprostejši dostop premaknite aplikacije na začetni zaslon."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Upravlja vaša organizacija"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Obvestila in aplikacije – izklopljeno"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index d260a4b..e79f0d4 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Punë"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profili i punës"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Gjej këtu aplikacionet e punës"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Secili aplikacion pune ka një distinktiv portokalli dhe mbahet i sigurt nga organizata jote. Zhvendosi aplikacionet e punës në ekranin tënd kryesor për qasje më të lehtë."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Menaxhohet nga organizata jote"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Njoftimet dhe aplikacionet janë joaktive"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index c190e75..7d5e28d 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -138,8 +138,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Пословне"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Профил за Work"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Пронађите пословне апликације овде"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Свака пословна апликација има наранџасту значку и штити је ваша организација. Преместите апликације на почетни екран да бисте им лакше приступали."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Овим управља организација"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Обавештења и апликације су искључени"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 85e814b..c886780 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Arbete"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Här hittar du jobbappar"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Alla jobbappar har ett orange märke och organisationen ser till att de är skyddade. Flytta apparna till startskärmen så kommer du åt dem lättare."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Hanteras av organisationen"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Aviseringar och appar är inaktiverade"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index a9dc8a6..8925c89 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Kazini"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Wasifu wa kazini"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Pata programu za kazi hapa"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Kila programu ya kazi ina beji ya rangi ya machungwa na hulindwa na shirika lako. Hamishia programu kwenye skrini yako ya kwanza ili uzifikie kwa urahisi."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Inasimamiwa na shirika lako"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Vipenge vya arifa na programu vimezimwa"</string>
</resources>
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index f322e9f..72894dc 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -26,6 +26,7 @@
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
+ <item name="android:keyboardLayout">@layout/search_container_all_apps</item>
</style>
<!-- Workspace -->
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 25bfc02..fc68b82 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"பணி"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"பணி விவரம்"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"பணி ஆப்ஸை இங்கு காணலாம்"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ஒவ்வொரு பணிப் பயன்பாடும் ஆரஞ்சு நிற பேட்ஜைக் கொண்டிருக்கும். இவை, உங்கள் நிறுவனத்தால் பாதுகாப்பாக வைக்கப்பட்டுள்ளன. இந்த ஆப்ஸை எளிதாக அணுக, முகப்புத் திரைக்கு நகர்த்திக்கொள்ளவும்."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"உங்கள் நிறுவனம் நிர்வகிக்கிறது"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ஆப்ஸும் அறிவிப்புகளும் ஆஃப் செய்யப்பட்டுள்ளன"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 6af5011..fb8a71b 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"కార్యాలయం"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"కార్యాలయ ప్రొఫైల్"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"కార్యాలయ యాప్లను ఇక్కడ కనుగొనండి"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ప్రతి కార్యాలయ యాప్కు నారింజ బ్యాడ్జ్ ఉంది మరియు మీ సంస్థ ద్వారా సురక్షితంగా ఉంచబడుతుంది. సులభ యాక్సెస్ కోసం యాప్లను మీ హోమ్ స్క్రీన్కి తరలించండి."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"మీ సంస్థ ద్వారా నిర్వహించబడతాయి"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"నోటిఫికేషన్లు మరియు యాప్లు ఆఫ్ చేయబడ్డాయి"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 866bfc9..6f2bc71 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"งาน"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"โปรไฟล์งาน"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"หาแอปงานที่นี่"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"แอปงานแต่ละแอปมีป้ายสีส้มและได้รับการรักษาความปลอดภัยจากองค์กรของคุณ ย้ายแอปไปยังหน้าจอหลักเพื่อให้เข้าถึงได้ง่ายขึ้น"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"จัดการโดยองค์กร"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ปิดการแจ้งเตือนและแอปอยู่"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index d0a820d..5aad29a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Trabaho"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profile sa trabaho"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Maghanap ng mga app para sa trabaho rito"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Ang bawat app para sa trabaho ay may orange na badge at pinapanatiling ligtas ng iyong organisasyon. Ilipat ang mga app sa iyong Home screen para mas madaling ma-access."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Pinamamahalaan ng iyong organisasyon"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Naka-off ang mga notification at app"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1f75b67..a0ebe6f0 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"İş"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"İş profili"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"İş uygulamalarını burada bulabilirsiniz"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Her iş uygulamasında, uygulama güvenliğinin kuruluşunuz tarafından sağlandığını gösteren turuncu bir rozet bulunur. Uygulamaları daha kolay erişim için Ana ekranınıza taşıyın."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Kuruluşunuz tarafından yönetiliyor"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Bildirimler ve uygulamalar kapalı"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 095ff5b..ffced57 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -139,8 +139,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Робочі додатки"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Робочий профіль"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Робочі додатки містяться тут"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Кожний робочий додаток має оранжевий значок. Його захищає організація. Для швидкого доступу перенесіть додатки на головний екран."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Профілем керує ваша організація"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Сповіщення та додатки вимкнено"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index ea8a4d7..cd65738 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"دفتری"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"دفتری پروفائل"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"یہاں دفتری ایپس تلاش کریں"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"ہر دفتری ایپ میں نارنجی بَیج ہوتا ہے اور اسے آپ کی تنظیم محفوظ رکھتی ہے۔ زیادہ آسان رسائی کیلئے ایپس کو اپنی ہوم اسکرین پر منتقل کریں۔"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"آپ کی تنظیم کے زیر انتظام"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"اطلاعات اور ایپس آف ہیں"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 70dc41f..200c2c0 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Ishchi"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Ishchi profil"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Ishga oid ilovalarni shu yerdan topish mumkin"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Apelsinrangli nishonga ega har bir ishga oid ilova tashkilotingiz tomonidan himoyalanadi. Ishga oid ilovalarga osonroq kirish uchun ularni bosh ekranga chiqaring."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Tashkilotingiz tomonidan boshqariladi"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Bildirishnomalar va ilovalar faol emas"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 1b8558f..a5b4892 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Cơ quan"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Hồ sơ công việc"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Tìm ứng dụng công việc tại đây"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Mỗi ứng dụng công việc đều có một huy hiệu màu cam và được tổ chức của bạn bảo mật. Bạn có thể di chuyển ứng dụng đến Màn hình chính để truy cập dễ dàng hơn."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Do tổ chức của bạn quản lý"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Thông báo và ứng dụng đang tắt"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 6941257..71e06e7 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"工作"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"工作资料"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"请在此处查找工作应用"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"每个工作应用均有一个橙色徽标,并由贵单位负责确保其安全。请将工作应用移到主屏幕,以便轻松访问。"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"由贵单位管理"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"通知和应用均已关闭"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 86c5246..7f792f8 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"商務"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"工作設定檔"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"請在此處尋找工作應用程式"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"每個工作應用程式都有橙色徽章,並由您的機構負責保持安全。您可以將工作應用程式移至主畫面,以便輕鬆存取。"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"由您的機構管理"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"通知和應用程式已關閉"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 1bac58a..b95e838 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"公司"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Work 設定檔"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"在這裡尋找辦公應用程式"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"每個辦公應用程式都有橘色徽章,並由貴機構負責管理及確保其安全。請將辦公應用程式移至主螢幕以便輕鬆存取。"</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"由貴機構所管理"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"已關閉通知和應用程式"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 323144b..65d7d4c 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -137,8 +137,7 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Umsebenzi"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Iphrofayela yomsebenzi"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Thola izinhlelo zokusebenza lapha"</string>
- <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
- <skip />
+ <string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Uhlo lokusebenza ngalunye lomsebenzi linebheji ewolintshi futhi igcinwa iphephile inhlangano yakho. Hambisa izinhlelo zokusebenza esikrinini sakho sasekhaya ngokufinyelela okulula."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Kuphethwe inhlangano yakho"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Izaziso nezinhlelo zokusebenza kuvaliwe"</string>
</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 64ca05e..1351dfa 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -44,6 +44,7 @@
<enum name="widget_section" value="3" />
<enum name="shortcut_popup" value="4" />
</attr>
+ <attr name="deferShadowGeneration" format="boolean" />
<attr name="centerVertically" format="boolean" />
</declare-styleable>
diff --git a/res/values/config.xml b/res/values/config.xml
index 3f727cf..065ad36 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -116,9 +116,6 @@
<!-- View ID used by cell layout to jail its content -->
<item type="id" name="cell_layout_jail_id" />
- <!-- Tag id used for view scrim -->
- <item type="id" name="view_scrim" />
-
<!-- Popup items -->
<integer name="config_popupOpenCloseDuration">150</integer>
<integer name="config_popupArrowOpenDuration">80</integer>
@@ -127,7 +124,7 @@
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
<item type="id" name="action_uninstall" />
- <item type="id" name="action_reconfigure" />
+ <item type="id" name="action_info" />
<item type="id" name="action_add_to_workspace" />
<item type="id" name="action_move" />
<item type="id" name="action_move_to_workspace" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f8f9c2a..1f46844 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -23,6 +23,10 @@
<dimen name="dynamic_grid_min_page_indicator_size">24dp</dimen>
<dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
<dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
+ <dimen name="dynamic_grid_overview_min_icon_zone_height">80dp</dimen>
+ <dimen name="dynamic_grid_overview_max_icon_zone_height">120dp</dimen>
+ <dimen name="dynamic_grid_overview_bar_item_width">80dp</dimen>
+ <dimen name="dynamic_grid_overview_bar_spacer_width">25dp</dimen>
<dimen name="dynamic_grid_workspace_top_padding">8dp</dimen>
<dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
<!-- Minimum space between workspace and hotseat in spring loaded mode -->
@@ -75,7 +79,7 @@
<!-- All Apps -->
<dimen name="all_apps_button_scale_down">0dp</dimen>
<dimen name="all_apps_search_bar_field_height">48dp</dimen>
- <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
+ <dimen name="all_apps_search_bar_height">60dp</dimen>
<dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
<dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
<dimen name="all_apps_background_canvas_width">700dp</dimen>
@@ -223,5 +227,5 @@
<dimen name="swipe_helper_falsing_threshold">70dp</dimen>
<!-- Overview -->
- <dimen name="options_menu_icon_size">24dp</dimen>
+ <dimen name="options_menu_icon_size">48dp</dimen>
</resources>
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
index 1367174..fea17b1 100644
--- a/res/values/drawables.xml
+++ b/res/values/drawables.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<resources>
- <drawable name="ic_setup_shadow">@drawable/ic_setting</drawable>
+ <drawable name="ic_info_shadow">@drawable/ic_info_no_shadow</drawable>
<drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
<drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b39cb04..381830c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -80,9 +80,6 @@
<!-- All applications label -->
<string name="all_apps_button_label">Apps list</string>
- <string name="all_apps_button_personal_label">Personal apps list</string>
- <string name="all_apps_button_work_label">Work apps list</string>
-
<!-- Label for button in all applications label to go back home (to the workspace / desktop)
for accessibilty (spoken when the button gets focus). -->
<string name="all_apps_home_button_label">Home</string>
@@ -172,8 +169,16 @@
<string name="settings_button_text">Home settings</string>
<!-- Message shown when a feature is disabled by the administrator -->
<string name="msg_disabled_by_admin">Disabled by your admin</string>
+ <!-- Text for custom accessibility action to go to the overview mode, where users can look and change the overall UI of the launcher. -->
+ <string name="accessibility_action_overview">Overview</string>
<!-- Strings for settings -->
+ <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
+ <string name="allow_rotation_title">Allow Home screen rotation</string>
+ <!-- Text explaining when the home screen will get rotated. [CHAR LIMIT=100] -->
+ <string name="allow_rotation_desc">When phone is rotated</string>
+ <!-- Text explaining that rotation is disabled in Display settings. 'Display' refers to the Display section in system settings [CHAR LIMIT=100] -->
+ <string name="allow_rotation_blocked_desc">Current Display setting doesn\'t permit rotation</string>
<!-- Title for Notification dots setting. Tapping this will link to the system Notifications settings screen where the user can turn off notification dots globally. [CHAR LIMIT=50] -->
<string name="icon_badging_title">Notification dots</string>
<!-- Text to indicate that the system icon badging setting is on [CHAR LIMIT=100] -->
@@ -186,8 +191,6 @@
<string name="msg_missing_notification_access">To show Notification Dots, turn on app notifications for <xliff:g id="name" example="My App">%1$s</xliff:g></string>
<!-- Button text in the confirmation dialog which would take the user to the system settings [CHAR LIMIT=50] -->
<string name="title_change_settings">Change settings</string>
- <!-- Summary for Notification dots setting. Tapping this will link enable/disable notification dots feature on the home screen. [CHAR LIMIT=50] -->
- <string name="icon_badging_service_title">Show notification dots</string>
<!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=40] -->
<string name="auto_add_shortcuts_label">Add icon to Home screen</string>
@@ -279,6 +282,15 @@
<!-- Accessibility action to move an item from folder to workspace. [CHAR_LIMIT=30] -->
<string name="action_move_to_workspace">Move to Home screen</string>
+ <!-- Accessibility action to move an homescreen to the left. [CHAR_LIMIT=30] -->
+ <string name="action_move_screen_left">Move screen to left</string>
+
+ <!-- Accessibility action to move an homescreen to the right. [CHAR_LIMIT=30] -->
+ <string name="action_move_screen_right">Move screen to right</string>
+
+ <!-- Accessibility confirmation when a screen was moved. -->
+ <string name="screen_moved">Screen moved</string>
+
<!-- Accessibility action to resize a widget. [CHAR_LIMIT=30] -->
<string name="action_resize">Resize</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ac6a6b1..8cc4743 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -25,6 +25,7 @@
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:colorEdgeEffect">#FF757575</item>
+ <item name="android:keyboardLayout">@layout/search_container_all_apps</item>
</style>
<style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 7bb19f3..28a35b8 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -37,6 +37,13 @@
android:persistent="true"
/>
+ <SwitchPreference
+ android:key="pref_allowRotation"
+ android:title="@string/allow_rotation_title"
+ android:defaultValue="@bool/allow_rotation"
+ android:persistent="true"
+ />
+
<ListPreference
android:key="pref_override_icon_shape"
android:title="@string/icon_shape_override_label"
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 77a430b..12db3b6 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -20,8 +20,6 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
-import android.graphics.Point;
-import android.view.Display;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -104,17 +102,4 @@
mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
}
}
-
- /**
- * Sets the device profile, adjusting it accordingly in case of multi-window
- */
- protected void setDeviceProfile(DeviceProfile dp) {
- mDeviceProfile = dp;
- if (isInMultiWindowModeCompat()) {
- Display display = getWindowManager().getDefaultDisplay();
- Point mwSize = new Point();
- display.getSize(mwSize);
- mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
- }
- }
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 74b9cfa..cc13263 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -33,7 +33,8 @@
* <li> Enable fast scroller.
* </ul>
*/
-public abstract class BaseRecyclerView extends RecyclerView {
+public abstract class BaseRecyclerView extends RecyclerView
+ implements RecyclerView.OnItemTouchListener {
protected RecyclerViewFastScroller mScrollbar;
@@ -50,6 +51,12 @@
}
@Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ addOnItemTouchListener(this);
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
bindFastScrollbar();
@@ -62,8 +69,40 @@
onUpdateScrollbar(0);
}
- public RecyclerViewFastScroller getScrollbar() {
- return mScrollbar;
+ /**
+ * We intercept the touch handling only to support fast scrolling when initiated from the
+ * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ handleTouchEvent(ev);
+ }
+
+ /**
+ * Handles the touch event and determines whether to show the fast scroller (or updates it if
+ * it is already showing).
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ // Move to mScrollbar's coordinate system.
+ // We need to take parent into account (view pager's location)
+ ViewGroup parent = (ViewGroup) getParent();
+ int left = parent.getLeft() - mScrollbar.getLeft();
+ int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
+ ev.offsetLocation(left, top);
+ try {
+ return mScrollbar.handleTouchEvent(ev);
+ } finally {
+ ev.offsetLocation(-left, -top);
+ }
+ }
+
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
}
public int getScrollBarTop() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8b6d9f8..dbdb2dc 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -36,6 +37,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
+import android.view.ViewParent;
import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
@@ -46,6 +48,7 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.FolderIconPreviewVerifier;
import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.PackageItemInfo;
@@ -70,9 +73,13 @@
private final boolean mCenterVertically;
private final CheckLongPressHelper mLongPressHelper;
+ private final HolographicOutlineHelper mOutlineHelper;
private final StylusEventHelper mStylusEventHelper;
private final float mSlop;
+ private Bitmap mPressedBackground;
+
+ private final boolean mDeferShadowGenerationOnTouch;
private final boolean mLayoutHorizontal;
private final int mIconSize;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -140,6 +147,8 @@
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
+ mDeferShadowGenerationOnTouch =
+ a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
int defaultIconSize = grid.iconSizePx;
@@ -164,6 +173,7 @@
mLongPressHelper = new CheckLongPressHelper(this);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
+ mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
}
@@ -221,6 +231,7 @@
FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(info);
mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f);
+ iconDrawable.setIsDisabled(info.isDisabled());
setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
@@ -280,6 +291,13 @@
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
+ // So that the pressed outline is visible immediately on setStayPressed(),
+ // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
+ // to create it)
+ if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
+ mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
+ }
+
// If we're in a stylus button press, don't check for long press.
if (!mStylusEventHelper.inStylusButtonPressed()) {
mLongPressHelper.postCheckForLongPress();
@@ -287,6 +305,12 @@
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
+ // If we've touched down and up on an item, and it's still not "pressed", then
+ // destroy the pressed outline
+ if (!isPressed()) {
+ mPressedBackground = null;
+ }
+
mLongPressHelper.cancelLongPress();
break;
case MotionEvent.ACTION_MOVE:
@@ -300,6 +324,22 @@
void setStayPressed(boolean stayPressed) {
mStayPressed = stayPressed;
+ if (!stayPressed) {
+ HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
+ mPressedBackground = null;
+ } else {
+ if (mPressedBackground == null) {
+ mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
+ }
+ }
+
+ // Only show the shadow effect when persistent pressed state is set.
+ ViewParent parent = getParent();
+ if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) {
+ ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon(
+ this, mPressedBackground);
+ }
+
refreshDrawableState();
}
@@ -316,12 +356,26 @@
}
@Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (super.onKeyDown(keyCode, event)) {
+ // Pre-create shadow so show immediately on click.
+ if (mPressedBackground == null) {
+ mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Unlike touch events, keypress event propagate pressed state change immediately,
// without waiting for onClickHandler to execute. Disable pressed state changes here
// to avoid flickering.
mIgnorePressedStateChange = true;
boolean result = super.onKeyUp(keyCode, event);
+
+ mPressedBackground = null;
mIgnorePressedStateChange = false;
refreshDrawableState();
return result;
@@ -609,4 +663,11 @@
public int getIconSize() {
return mIconSize;
}
+
+ /**
+ * Interface to be implemented by the grand parent to allow click shadow effect.
+ */
+ public interface BubbleTextShadowHandler {
+ void setPressedIcon(BubbleTextView icon, Bitmap background);
+ }
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index c866880..19ee0b8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -108,12 +108,6 @@
setContentDescription(mText);
}
- protected void updateText(int resId) {
- setText(resId);
- mText = getText();
- setContentDescription(mText);
- }
-
protected void setDrawable(int resId) {
// We do not set the drawable in the xml as that inflates two drawables corresponding to
// drawableLeft and drawableStart.
@@ -242,7 +236,9 @@
protected abstract boolean supportsDrop(ItemInfo info);
- public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view);
+ public boolean supportsAccessibilityDrop(ItemInfo info) {
+ return supportsDrop(info);
+ }
@Override
public boolean isDropEnabled() {
@@ -372,6 +368,4 @@
TextUtils.TruncateAt.END);
return !mText.equals(displayedText);
}
-
- public abstract int getControlTypeForLogging();
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 7979082..5e4f670 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -21,7 +21,6 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -46,6 +45,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
@@ -70,7 +70,7 @@
import java.util.Comparator;
import java.util.Stack;
-public class CellLayout extends ViewGroup {
+public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
@@ -128,6 +128,8 @@
private int mDragOutlineCurrent = 0;
private final Paint mDragOutlinePaint = new Paint();
+ private final ClickShadowView mTouchFeedbackView;
+
@Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
@Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
@@ -283,6 +285,9 @@
mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
+
+ mTouchFeedbackView = new ClickShadowView(context);
+ addView(mTouchFeedbackView);
addView(mShortcutsAndWidgets);
}
@@ -292,7 +297,7 @@
ViewCompat.setAccessibilityDelegate(this, null);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- setOnClickListener(null);
+ setOnClickListener(mLauncher);
} else {
if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
!(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
@@ -379,6 +384,11 @@
return mDropPending;
}
+ @Override
+ public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+ mTouchFeedbackView.setPressedIcon(icon, background);
+ }
+
void setIsDragOverlapping(boolean isDragOverlapping) {
if (mIsDragOverlapping != isDragOverlapping) {
mIsDragOverlapping = isDragOverlapping;
@@ -775,6 +785,13 @@
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
}
+ // Make the feedback view large enough to hold the blur bitmap.
+ mTouchFeedbackView.measure(
+ MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
+ MeasureSpec.EXACTLY));
+
mShortcutsAndWidgets.measure(
MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
@@ -798,7 +815,11 @@
int top = getPaddingTop();
int bottom = b - t - getPaddingBottom();
+ mTouchFeedbackView.layout(left, top,
+ left + mTouchFeedbackView.getMeasuredWidth(),
+ top + mTouchFeedbackView.getMeasuredHeight());
mShortcutsAndWidgets.layout(left, top, right, bottom);
+
// Expand the background drawing bounds by the padding baked into the background drawable
mBackground.getPadding(mTempRect);
mBackground.setBounds(
@@ -991,7 +1012,6 @@
}
}
- @SuppressLint("StringFormatMatches")
public String getItemMoveDescription(int cellX, int cellY) {
if (mContainerType == HOTSEAT) {
return getContext().getString(R.string.move_to_hotseat_position,
diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java
new file mode 100644
index 0000000..5391b4d
--- /dev/null
+++ b/src/com/android/launcher3/ClickShadowView.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 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 static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
+import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR;
+import static com.android.launcher3.LauncherAnimUtils.ELEVATION;
+import static com.android.launcher3.graphics.HolographicOutlineHelper.ADAPTIVE_ICON_SHADOW_BITMAP;
+
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+
+public class ClickShadowView extends View {
+
+ private static final int SHADOW_SIZE_FACTOR = 3;
+ private static final int SHADOW_LOW_ALPHA = 30;
+ private static final int SHADOW_HIGH_ALPHA = 60;
+
+ private static float sAdaptiveIconScaleFactor = 1f;
+
+ private final Paint mPaint;
+
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private final float mShadowOffset;
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private final float mShadowPadding;
+
+ private Bitmap mBitmap;
+ private ObjectAnimator mAnim;
+
+ private Drawable mAdaptiveIcon;
+ private ViewOutlineProvider mOutlineProvider;
+
+ public ClickShadowView(Context context) {
+ super(context);
+ mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ mPaint.setColor(Color.BLACK);
+
+ mShadowPadding = getResources().getDimension(R.dimen.blur_size_click_shadow);
+ mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
+ }
+
+ public static void setAdaptiveIconScaleFactor(float factor) {
+ sAdaptiveIconScaleFactor = factor;
+ }
+
+ /**
+ * @return extra space required by the view to show the shadow.
+ */
+ public int getExtraSize() {
+ return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
+ }
+
+ public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+ if (icon == null) {
+ setBitmap(null);
+ cancelAnim();
+ return;
+ }
+ if (background == null) {
+ if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+ // clear animation shadow
+ }
+ setBitmap(null);
+ cancelAnim();
+ icon.setOutlineProvider(null);
+ } else if (setBitmap(background)) {
+ if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+ setupAdaptiveShadow(icon);
+ cancelAnim();
+ startAnim(icon, ELEVATION,
+ getResources().getDimension(R.dimen.click_shadow_elevation));
+ } else {
+ alignWithIconView(icon);
+ startAnim(this, ALPHA, 1);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ private void setupAdaptiveShadow(final BubbleTextView view) {
+ if (mAdaptiveIcon == null) {
+ mAdaptiveIcon = new AdaptiveIconDrawable(null, null);
+ mOutlineProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ mAdaptiveIcon.getOutline(outline);
+ }
+ };
+ }
+
+ int iconWidth = view.getRight() - view.getLeft();
+ int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
+ int drawableWidth = view.getIcon().getBounds().width();
+
+ Rect bounds = new Rect();
+ bounds.left = view.getCompoundPaddingLeft() + (iconHSpace - drawableWidth) / 2;
+ bounds.right = bounds.left + drawableWidth;
+ bounds.top = view.getPaddingTop();
+ bounds.bottom = bounds.top + view.getIcon().getBounds().height();
+ Utilities.scaleRectAboutCenter(bounds, sAdaptiveIconScaleFactor);
+
+ mAdaptiveIcon.setBounds(bounds);
+ view.setOutlineProvider(mOutlineProvider);
+ }
+
+ /**
+ * Applies the new bitmap.
+ * @return true if the view was invalidated.
+ */
+ private boolean setBitmap(Bitmap b) {
+ if (b != mBitmap){
+ mBitmap = b;
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mBitmap != null) {
+ mPaint.setAlpha(SHADOW_LOW_ALPHA);
+ canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+ mPaint.setAlpha(SHADOW_HIGH_ALPHA);
+ canvas.drawBitmap(mBitmap, 0, mShadowOffset, mPaint);
+ }
+ }
+
+ private void cancelAnim() {
+ if (mAnim != null) {
+ mAnim.cancel();
+ mAnim.setCurrentPlayTime(0);
+ mAnim = null;
+ }
+ }
+
+ private void startAnim(View target, Property<View, Float> property, float endValue) {
+ cancelAnim();
+ property.set(target, 0f);
+ mAnim = ObjectAnimator.ofFloat(target, property, endValue);
+ mAnim.setDuration(CLICK_FEEDBACK_DURATION)
+ .setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+ mAnim.start();
+ }
+
+ /**
+ * Aligns the shadow with {@param view}
+ * Note: {@param view} must be a descendant of my parent.
+ */
+ private void alignWithIconView(BubbleTextView view) {
+ int[] coords = new int[] {0, 0};
+ Utilities.getDescendantCoordRelativeToAncestor(
+ (ViewGroup) view.getParent(), (View) getParent(), coords, false);
+
+ float leftShift = view.getLeft() + coords[0] - getLeft();
+ float topShift = view.getTop() + coords[1] - getTop();
+ int iconWidth = view.getRight() - view.getLeft();
+ int iconHeight = view.getBottom() - view.getTop();
+ int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
+ float drawableWidth = view.getIcon().getBounds().width();
+
+ // Set the bounds to clip against
+ int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
+ int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
+ setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
+
+ setTranslationX(leftShift
+ + view.getCompoundPaddingLeft() * view.getScaleX()
+ + (iconHSpace - drawableWidth) * view.getScaleX() / 2 /* drawable gap */
+ + iconWidth * (1 - view.getScaleX()) / 2 /* gap due to scale */
+ - mShadowPadding /* extra shadow size */
+ );
+ setTranslationY(topShift
+ + view.getPaddingTop() * view.getScaleY() /* drawable gap */
+ + view.getHeight() * (1 - view.getScaleY()) / 2 /* gap due to scale */
+ - mShadowPadding /* extra shadow size */
+ );
+ }
+}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 28d1129..c12ea57 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -24,7 +24,6 @@
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
public class DeleteDropTarget extends ButtonDropTarget {
@@ -55,7 +54,7 @@
* @return true for items that should have a "Remove" action in accessibility.
*/
@Override
- public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
+ public boolean supportsAccessibilityDrop(ItemInfo info) {
return (info instanceof ShortcutInfo)
|| (info instanceof LauncherAppWidgetInfo)
|| (info instanceof FolderInfo);
@@ -104,9 +103,4 @@
mLauncher.getDragLayer()
.announceForAccessibility(getContext().getString(R.string.item_removed));
}
-
- @Override
- public int getControlTypeForLogging() {
- return ControlType.REMOVE_TARGET;
- }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 13971ad..ba55b36 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -250,14 +250,6 @@
}
/**
- * Inverse of {@link #getMultiWindowProfile(Context, Point)}
- * @return device profile corresponding to the current orientation in non multi-window mode.
- */
- public DeviceProfile getFullScreenProfile() {
- return isLandscape ? inv.landscapeProfile : inv.portraitProfile;
- }
-
- /**
* Adjusts the profile so that the labels on the Workspace are hidden.
* It is important to call this method after the All Apps variables have been set.
*/
@@ -515,6 +507,12 @@
}
}
+ public boolean shouldIgnoreLongPressToOverview(float touchX) {
+ boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
+ boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
+ return !isMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
+ }
+
private static Context getContext(Context c, int orientation) {
Configuration context = new Configuration(c.getResources().getConfiguration());
context.orientation = orientation;
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 3873a81..c4ec8c9 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,9 +16,8 @@
package com.android.launcher3;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -29,8 +28,9 @@
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Property;
import android.util.SparseArray;
@@ -38,12 +38,14 @@
public class FastBitmapDrawable extends Drawable {
- private static final float PRESSED_SCALE = 1.1f;
-
+ private static final float PRESSED_BRIGHTNESS = 100f / 255f;
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
- public static final int CLICK_FEEDBACK_DURATION = 200;
+ public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = (input) ->
+ (input < 0.05f) ? (input / 0.05f) : ((input < 0.3f) ? 1 : (1 - input) / 0.7f);
+
+ public static final int CLICK_FEEDBACK_DURATION = 2000;
// Since we don't need 256^2 values for combinations of both the brightness and saturation, we
// reduce the value space to a smaller value V, which reduces the number of cached
@@ -58,29 +60,24 @@
private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
- protected Bitmap mBitmap;
+ private final Bitmap mBitmap;
protected final int mIconColor;
private boolean mIsPressed;
private boolean mIsDisabled;
- // Animator and properties for the fast bitmap drawable's scale
- private static final Property<FastBitmapDrawable, Float> SCALE
- = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
+ private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
+ = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
@Override
public Float get(FastBitmapDrawable fastBitmapDrawable) {
- return fastBitmapDrawable.mScale;
+ return fastBitmapDrawable.getBrightness();
}
@Override
public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
- fastBitmapDrawable.mScale = value;
- fastBitmapDrawable.invalidateSelf();
+ fastBitmapDrawable.setBrightness(value);
}
};
- private ObjectAnimator mScaleAnimation;
- private float mScale = 1;
-
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -89,6 +86,9 @@
private int mAlpha = 255;
private int mPrevUpdateKey = Integer.MAX_VALUE;
+ // Animators for the fast bitmap drawable's brightness
+ private ObjectAnimator mBrightnessAnimator;
+
public FastBitmapDrawable(Bitmap b) {
this(b, Color.TRANSPARENT);
}
@@ -108,20 +108,8 @@
}
@Override
- public final void draw(Canvas canvas) {
- if (mScaleAnimation != null) {
- int count = canvas.save();
- Rect bounds = getBounds();
- canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
- drawInternal(canvas, bounds);
- canvas.restoreToCount(count);
- } else {
- drawInternal(canvas, getBounds());
- }
- }
-
- protected void drawInternal(Canvas canvas, Rect bounds) {
- canvas.drawBitmap(mBitmap, null, bounds, mPaint);
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
@Override
@@ -150,10 +138,6 @@
return mAlpha;
}
- public float getAnimatedScale() {
- return mScaleAnimation == null ? 1 : mScale;
- }
-
@Override
public int getIntrinsicWidth() {
return mBitmap.getWidth();
@@ -200,20 +184,19 @@
if (mIsPressed != isPressed) {
mIsPressed = isPressed;
- if (mScaleAnimation != null) {
- mScaleAnimation.cancel();
- mScaleAnimation = null;
+ if (mBrightnessAnimator != null) {
+ mBrightnessAnimator.cancel();
}
if (mIsPressed) {
// Animate when going to pressed state
- mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
- mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
- mScaleAnimation.setInterpolator(ACCEL);
- mScaleAnimation.start();
+ mBrightnessAnimator = ObjectAnimator.ofFloat(
+ this, BRIGHTNESS, getExpectedBrightness());
+ mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
+ mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+ mBrightnessAnimator.start();
} else {
- mScale = 1f;
- invalidateSelf();
+ setBrightness(getExpectedBrightness());
}
return true;
}
@@ -222,7 +205,12 @@
private void invalidateDesaturationAndBrightness() {
setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
- setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
+ setBrightness(getExpectedBrightness());
+ }
+
+ private float getExpectedBrightness() {
+ return mIsDisabled ? DISABLED_BRIGHTNESS :
+ (mIsPressed ? PRESSED_BRIGHTNESS : 0);
}
public void setIsDisabled(boolean isDisabled) {
@@ -324,9 +312,10 @@
return new MyConstantState(mBitmap, mIconColor);
}
- protected static class MyConstantState extends ConstantState {
- protected final Bitmap mBitmap;
- protected final int mIconColor;
+ private static class MyConstantState extends ConstantState {
+ private final Bitmap mBitmap;
+ private final int mIconColor;
+
public MyConstantState(Bitmap bitmap, int color) {
mBitmap = bitmap;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 211a756..03043f2 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -63,6 +63,14 @@
return mContent;
}
+ /**
+ * Registers the specified listener on the cell layout of the hotseat.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mContent.setOnLongClickListener(l);
+ }
+
/* Get the orientation invariant order of the item in the hotseat for persistence. */
int getOrderInHotseat(int x, int y) {
return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index ab73074..d0581a2 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -45,10 +45,12 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.graphics.ColorExtractor;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.Preconditions;
@@ -124,7 +126,7 @@
// automatically be loaded as ALPHA_8888.
mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
- if (BitmapRenderer.USE_HARDWARE_BITMAP) {
+ if (UiFactory.USE_HARDWARE_BITMAP) {
mHighResOptions = new BitmapFactory.Options();
mHighResOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
} else {
@@ -641,8 +643,11 @@
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
BitmapInfo iconInfo = li.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
- mInstantAppResolver.isInstantApp(appInfo));
+ appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
+ if (mInstantAppResolver.isInstantApp(appInfo)) {
+ li.badgeWithDrawable(iconInfo.icon,
+ mContext.getDrawable(R.drawable.ic_instant_app_badge));
+ }
li.recycle();
Bitmap lowResIcon = generateLowResIcon(iconInfo.icon);
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
new file mode 100644
index 0000000..e52fd76
--- /dev/null
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 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.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Themes;
+
+public class InfoDropTarget extends UninstallDropTarget {
+
+ private static final String TAG = "InfoDropTarget";
+
+ public InfoDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public InfoDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void setupUi() {
+ // Get the hover color
+ mHoverColor = Themes.getColorAccent(getContext());
+ setDrawable(R.drawable.ic_info_shadow);
+ }
+
+ @Override
+ protected ComponentName performDropAction(ItemInfo item) {
+ return performDropAction(mLauncher, item, null, null);
+ }
+
+ /**
+ * @return Whether the activity was started.
+ */
+ public static boolean startDetailsActivityForInfo(
+ ItemInfo info, Launcher launcher, Rect sourceBounds, Bundle opts) {
+ return performDropAction(launcher, info, sourceBounds, opts) != null;
+ }
+
+ /**
+ * Performs the drop action and returns the target component for the dragObject or null if
+ * the action was not performed.
+ */
+ private static ComponentName performDropAction(Context context, ItemInfo info,
+ Rect sourceBounds, Bundle opts) {
+ if (info instanceof PromiseAppInfo) {
+ PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
+ context.startActivity(promiseAppInfo.getMarketIntent(context));
+ return null;
+ }
+ ComponentName componentName = null;
+ if (info instanceof AppInfo) {
+ componentName = ((AppInfo) info).componentName;
+ } else if (info instanceof ShortcutInfo) {
+ componentName = info.getTargetComponent();
+ } else if (info instanceof PendingAddItemInfo) {
+ componentName = ((PendingAddItemInfo) info).componentName;
+ } else if (info instanceof LauncherAppWidgetInfo) {
+ componentName = ((LauncherAppWidgetInfo) info).providerName;
+ }
+ if (componentName != null) {
+ try {
+ LauncherAppsCompat.getInstance(context)
+ .showAppDetailsForProfile(componentName, info.user, sourceBounds, opts);
+ return componentName;
+ } catch (SecurityException | ActivityNotFoundException e) {
+ Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Unable to launch settings", e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int getAccessibilityAction() {
+ return LauncherAccessibilityDelegate.INFO;
+ }
+
+ @Override
+ protected boolean supportsDrop(ItemInfo info) {
+ // Only show the App Info drop target if developer settings are enabled.
+ boolean developmentSettingsEnabled = Settings.Global.getInt(
+ getContext().getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
+ if (!developmentSettingsEnabled) {
+ return false;
+ }
+ return info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT &&
+ (info instanceof AppInfo ||
+ (info instanceof ShortcutInfo && !((ShortcutInfo) info).isPromise()) ||
+ (info instanceof LauncherAppWidgetInfo &&
+ ((LauncherAppWidgetInfo) info).restoreStatus == 0) ||
+ info instanceof PendingAddItemInfo);
+ }
+}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index f63cce5..e460911 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -22,7 +22,6 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Point;
-import android.support.annotation.VisibleForTesting;
import android.util.DisplayMetrics;
import android.util.Xml;
import android.view.Display;
@@ -89,18 +88,17 @@
public Point defaultWallpaperSize;
- @VisibleForTesting
public InvariantDeviceProfile() {
}
- private InvariantDeviceProfile(InvariantDeviceProfile p) {
+ public InvariantDeviceProfile(InvariantDeviceProfile p) {
this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns,
p.numFolderRows, p.numFolderColumns,
p.iconSize, p.landscapeIconSize, p.iconTextSize, p.numHotseatIcons,
p.defaultLayoutId, p.demoModeLayoutId);
}
- private InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
+ InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
float is, float lis, float its, int hs, int dlId, int dmlId) {
name = n;
minWidthDps = w;
@@ -118,7 +116,7 @@
}
@TargetApi(23)
- public InvariantDeviceProfile(Context context) {
+ InvariantDeviceProfile(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0bbb90c..55bfef6 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,7 +18,13 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -31,8 +37,10 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
+import android.app.AlertDialog;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
@@ -40,13 +48,17 @@
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Build;
@@ -61,12 +73,16 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.ActionMode;
+import android.view.Display;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
@@ -85,8 +101,10 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -98,9 +116,8 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -117,7 +134,6 @@
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -139,8 +155,10 @@
/**
* Default launcher application.
*/
-public class Launcher extends BaseActivity implements LauncherExterns, LauncherModel.Callbacks,
- LauncherProviderChangeListener, WallpaperColorInfo.OnThemeChangeListener {
+public class Launcher extends BaseActivity
+ implements LauncherExterns, View.OnClickListener, OnLongClickListener,
+ LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
+ WallpaperColorInfo.OnThemeChangeListener {
public static final String TAG = "Launcher";
static final boolean LOGD = false;
@@ -153,8 +171,8 @@
private static final int REQUEST_PICK_WALLPAPER = 10;
private static final int REQUEST_BIND_APPWIDGET = 11;
- public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
- public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+ private static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
+ private static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
private static final int REQUEST_PERMISSION_CALL_PHONE = 14;
@@ -218,7 +236,7 @@
AllAppsTransitionController mAllAppsController;
// UI and state for the overview panel
- private View mOverviewPanel;
+ private ViewGroup mOverviewPanel;
@Thunk boolean mWorkspaceLoading = true;
@@ -251,10 +269,13 @@
*/
private PendingRequestArgs mPendingRequestArgs;
+ private final PointF mLastDispatchTouchEvent = new PointF();
+
public ViewGroupFocusHelper mFocusHandler;
+ private boolean mRotationEnabled = false;
private boolean mAppLaunchSuccess;
- private RotationHelper mRotationHelper;
+ private RotationPrefChangeHandler mRotationPrefChangeHandler;
private ActionMode mCurrentActionMode;
@Override
@@ -306,10 +327,20 @@
setupViews();
mPopupDataProvider = new PopupDataProvider(this);
- mRotationHelper = new RotationHelper(this);
+ mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
+ // In case we are on a device with locked rotation, we should look at preferences to check
+ // if the user has specifically allowed rotation.
+ if (!mRotationEnabled) {
+ mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
+ mRotationPrefChangeHandler = new RotationPrefChangeHandler();
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
+ }
boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
if (internalStateHandled) {
+ // Temporarily enable the rotation
+ mRotationEnabled = true;
+
if (savedInstanceState != null) {
// InternalStateHandler has already set the appropriate state.
// We dont need to do anything.
@@ -341,6 +372,7 @@
// For handling default keys
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ updateRequestedOrientation();
setContentView(mLauncherView);
getRootView().dispatchInsets();
@@ -359,52 +391,50 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
- mRotationHelper.initialize();
TraceHelper.endSection("Launcher-onCreate");
}
+ public void updateRequestedOrientation() {
+ // On large interfaces, or on devices that a user has specifically enabled screen rotation,
+ // we want the screen to auto-rotate based on the current orientation
+ setRequestedOrientation(mRotationEnabled
+ ? SCREEN_ORIENTATION_UNSPECIFIED : SCREEN_ORIENTATION_NOSENSOR);
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
mUserEventDispatcher = null;
initDeviceProfile(mDeviceProfile.inv);
- FileLog.d(TAG, "Config changed, my orientation=" +
- getResources().getConfiguration().orientation +
- ", new orientation=" + newConfig.orientation +
- ", old orientation=" + mOldConfig.orientation +
- ", isTransposed=" + mDeviceProfile.isVerticalBarLayout());
dispatchDeviceProfileChanged();
getRootView().dispatchInsets();
getStateManager().reapplyState();
// TODO: We can probably avoid rebind when only screen size changed.
- rebindModel();
+ int currentPage = mWorkspace.getNextPage();
+ if (mModel.startLoader(currentPage)) {
+ mWorkspace.setCurrentPage(currentPage);
+ setWorkspaceLoading(true);
+ }
}
mOldConfig.setTo(newConfig);
super.onConfigurationChanged(newConfig);
}
- @Override
- public void rebindModel() {
- int currentPage = mWorkspace.getNextPage();
- if (mModel.startLoader(currentPage)) {
- mWorkspace.setCurrentPage(currentPage);
- setWorkspaceLoading(true);
- }
- }
-
private void initDeviceProfile(InvariantDeviceProfile idp) {
// Load configuration-specific DeviceProfile
- setDeviceProfile(idp.getDeviceProfile(this));
- mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
- }
-
- public RotationHelper getRotationHelper() {
- return mRotationHelper;
+ mDeviceProfile = idp.getDeviceProfile(this);
+ if (isInMultiWindowModeCompat()) {
+ Display display = getWindowManager().getDefaultDisplay();
+ Point mwSize = new Point();
+ display.getSize(mwSize);
+ mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+ }
+ mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
}
@Override
@@ -794,7 +824,6 @@
mScrimAnimator.start();
}
mShouldFadeInScrim = false;
- UiFactory.onStart(this);
}
@Override
@@ -934,7 +963,6 @@
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
- mHotseat = findViewById(R.id.hotseat);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -943,6 +971,15 @@
// Setup the drag layer
mDragLayer.setup(this, mDragController);
+ // Setup the hotseat
+ mHotseat = (Hotseat) findViewById(R.id.hotseat);
+ if (mHotseat != null) {
+ mHotseat.setOnLongClickListener(this);
+ }
+
+ // Setup the workspace
+ mWorkspace.setHapticFeedbackEnabled(false);
+ mWorkspace.setOnLongClickListener(this);
mWorkspace.setup(mDragController);
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
@@ -960,7 +997,7 @@
mDragController.setMoveTarget(mWorkspace);
mDropTargetBar.setup(mDragController);
- mAllAppsController.setupViews(mAppsView, mHotseat);
+ mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
}
/**
@@ -984,7 +1021,7 @@
BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.app_icon, parent, false);
favorite.applyFromShortcutInfo(info);
- favorite.setOnClickListener(ItemClickHandler.INSTANCE);
+ favorite.setOnClickListener(this);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
@@ -1196,7 +1233,7 @@
return mHotseat;
}
- public <T extends View> T getOverviewPanel() {
+ public <T extends ViewGroup> T getOverviewPanel() {
return (T) mOverviewPanel;
}
@@ -1338,7 +1375,10 @@
mModel.stopLoader();
LauncherAppState.getInstance(this).setLauncher(null);
}
- mRotationHelper.destroy();
+
+ if (mRotationPrefChangeHandler != null) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
+ }
try {
mAppWidgetHost.stopListening();
@@ -1633,6 +1673,232 @@
}
/**
+ * Launches the intent referred by the clicked shortcut.
+ *
+ * @param v The view representing the clicked shortcut.
+ */
+ public void onClick(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) {
+ return;
+ }
+
+ if (!mWorkspace.isFinishedSwitchingState()) {
+ return;
+ }
+
+ if (v instanceof Workspace) {
+ if (isInState(OVERVIEW)) {
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
+ LauncherLogProto.Action.Direction.NONE,
+ LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
+ mStateManager.goToState(NORMAL);
+ }
+ return;
+ }
+
+ if (v instanceof CellLayout) {
+ if (isInState(OVERVIEW)) {
+ int page = mWorkspace.indexOfChild(v);
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
+ LauncherLogProto.Action.Direction.NONE,
+ LauncherLogProto.ContainerType.OVERVIEW, page);
+ mWorkspace.snapToPageFromOverView(page);
+ mStateManager.goToState(NORMAL);
+ }
+ return;
+ }
+
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ onClickAppShortcut(v);
+ } else if (tag instanceof FolderInfo) {
+ if (v instanceof FolderIcon) {
+ onClickFolderIcon(v);
+ }
+ } else if (tag instanceof AppInfo) {
+ startAppShortcutOrInfoActivity(v);
+ } else if (tag instanceof LauncherAppWidgetInfo) {
+ if (v instanceof PendingAppWidgetHostView) {
+ onClickPendingWidget((PendingAppWidgetHostView) v);
+ }
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Event handler for the app widget view which has not fully restored.
+ */
+ public void onClickPendingWidget(final PendingAppWidgetHostView v) {
+ if (mIsSafeModeEnabled) {
+ Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+ if (v.isReadyForClickSetup()) {
+ LauncherAppWidgetProviderInfo appWidgetInfo =
+ mAppWidgetManager.findProvider(info.providerName, info.user);
+ if (appWidgetInfo == null) {
+ return;
+ }
+ WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
+ if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
+ // This should not happen, as we make sure that an Id is allocated during bind.
+ return;
+ }
+ addFlowHandler.startBindFlow(this, info.appWidgetId, info,
+ REQUEST_BIND_PENDING_APPWIDGET);
+ } else {
+ addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
+ }
+ } else {
+ final String packageName = info.providerName.getPackageName();
+ onClickPendingAppItem(v, packageName, info.installProgress >= 0);
+ }
+ }
+
+ private void onClickPendingAppItem(final View v, final String packageName,
+ boolean downloadStarted) {
+ if (downloadStarted) {
+ // If the download has started, simply direct to the market app.
+ startMarketIntentForPackage(v, packageName);
+ return;
+ }
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.abandoned_promises_title)
+ .setMessage(R.string.abandoned_promise_explanation)
+ .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ startMarketIntentForPackage(v, packageName);
+ }
+ })
+ .setNeutralButton(R.string.abandoned_clean_this,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ final UserHandle user = Process.myUserHandle();
+ mWorkspace.removeAbandonedPromise(packageName, user);
+ }
+ })
+ .create().show();
+ }
+
+ private void startMarketIntentForPackage(View v, String packageName) {
+ ItemInfo item = (ItemInfo) v.getTag();
+ Intent intent = new PackageManagerHelper(v.getContext()).getMarketIntent(packageName);
+ startActivitySafely(v, intent, item);
+ }
+
+ /**
+ * Event handler for an app shortcut click.
+ *
+ * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
+ */
+ protected void onClickAppShortcut(final View v) {
+ if (LOGD) Log.d(TAG, "onClickAppShortcut");
+ Object tag = v.getTag();
+ if (!(tag instanceof ShortcutInfo)) {
+ throw new IllegalArgumentException("Input must be a Shortcut");
+ }
+
+ // Open shortcut
+ final ShortcutInfo shortcut = (ShortcutInfo) tag;
+
+ if (shortcut.isDisabled()) {
+ final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
+ if ((disabledFlags &
+ ~FLAG_DISABLED_SUSPENDED &
+ ~FLAG_DISABLED_QUIET_USER) == 0) {
+ // If the app is only disabled because of the above flags, launch activity anyway.
+ // Framework will tell the user why the app is suspended.
+ } else {
+ if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
+ // Use a message specific to this shortcut, if it has one.
+ Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ // Otherwise just use a generic error message.
+ int error = R.string.activity_not_available;
+ if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
+ error = R.string.safemode_shortcut_error;
+ } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+ (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
+ error = R.string.shortcut_not_available;
+ }
+ Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+
+ // Check for abandoned promise
+ if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
+ String packageName = shortcut.intent.getComponent() != null ?
+ shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+ if (!TextUtils.isEmpty(packageName)) {
+ onClickPendingAppItem(v, packageName,
+ shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
+ return;
+ }
+ }
+
+ // Start activities
+ startAppShortcutOrInfoActivity(v);
+ }
+
+ private void startAppShortcutOrInfoActivity(View v) {
+ ItemInfo item = (ItemInfo) v.getTag();
+ Intent intent;
+ if (item instanceof PromiseAppInfo) {
+ PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+ intent = promiseAppInfo.getMarketIntent(this);
+ } else {
+ intent = item.getIntent();
+ }
+ if (intent == null) {
+ throw new IllegalArgumentException("Input must have a valid intent");
+ }
+ if (item instanceof ShortcutInfo) {
+ ShortcutInfo si = (ShortcutInfo) item;
+ if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
+ && intent.getAction() == Intent.ACTION_VIEW) {
+ // make a copy of the intent that has the package set to null
+ // we do this because the platform sometimes disables instant
+ // apps temporarily (triggered by the user) and fallbacks to the
+ // web ui. This only works though if the package isn't set
+ intent = new Intent(intent);
+ intent.setPackage(null);
+ }
+ }
+ startActivitySafely(v, intent, item);
+ }
+
+ /**
+ * Event handler for a folder icon click.
+ *
+ * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
+ */
+ protected void onClickFolderIcon(View v) {
+ if (LOGD) Log.d(TAG, "onClickFolder");
+ if (!(v instanceof FolderIcon)){
+ throw new IllegalArgumentException("Input must be a FolderIcon");
+ }
+
+ Folder folder = ((FolderIcon) v).getFolder();
+ if (!folder.isOpen() && !folder.isDestroyed()) {
+ // Open the requested folder
+ folder.animateOpen();
+ }
+ }
+
+ /**
* Event handler for the wallpaper picker button that appears after a long press
* on the home screen.
*/
@@ -1789,6 +2055,82 @@
return mAppLaunchSuccess;
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ mLastDispatchTouchEvent.set(ev.getX(), ev.getY());
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!isDraggingEnabled()) return false;
+ if (isWorkspaceLocked()) return false;
+ if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
+
+ boolean ignoreLongPressToOverview =
+ mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEvent.x);
+
+ if (v instanceof Workspace) {
+ if (!isInState(OVERVIEW)) {
+ if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.WORKSPACE,
+ mWorkspace.getCurrentPage());
+ UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ CellLayout.CellInfo longClickCellInfo = null;
+ View itemUnderLongClick = null;
+ if (v.getTag() instanceof ItemInfo) {
+ ItemInfo info = (ItemInfo) v.getTag();
+ longClickCellInfo = new CellLayout.CellInfo(v, info);
+ itemUnderLongClick = longClickCellInfo.cell;
+ mPendingRequestArgs = null;
+ }
+
+ // The hotseat touch handling does not go through Workspace, and we always allow long press
+ // on hotseat items.
+ if (!mDragController.isDragging()) {
+ if (itemUnderLongClick == null) {
+ // User long pressed on empty space
+ if (mWorkspace.isPageRearrangeEnabled()) {
+ mWorkspace.startReordering(v);
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.OVERVIEW);
+ } else {
+ if (ignoreLongPressToOverview) {
+ return false;
+ }
+ getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+ Action.Direction.NONE, ContainerType.WORKSPACE,
+ mWorkspace.getCurrentPage());
+ UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
+ }
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ } else {
+ final boolean isAllAppsButton =
+ !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
+ mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat(
+ longClickCellInfo.cellX, longClickCellInfo.cellY));
+ if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
+ // User long pressed on an item
+ mWorkspace.startDrag(longClickCellInfo, new DragOptions());
+ }
+ }
+ }
+ return true;
+ }
+
boolean isHotseatLayout(View layout) {
// TODO: Remove this method
return mHotseat != null && layout != null &&
@@ -1824,7 +2166,6 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onTrimMemory(level);
}
- UiFactory.onTrimMemory(this, level);
}
@Override
@@ -1893,7 +2234,6 @@
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
- mAppWidgetHost.clearViews();
if (mHotseat != null) {
mHotseat.resetLayout();
@@ -2379,6 +2719,10 @@
mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
}
+ public boolean isRotationEnabled () {
+ return mRotationEnabled;
+ }
+
/**
* $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
*/
@@ -2408,20 +2752,18 @@
writer.println(prefix + " " + tag.toString());
}
}
+
+ try {
+ FileLog.flushAll(writer);
+ } catch (Exception e) {
+ // Ignore
+ }
}
writer.println(prefix + "Misc:");
writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
writer.println(" mPendingActivityResult=" + mPendingActivityResult);
- writer.println(" deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout());
- writer.println(" orientation=" + getResources().getConfiguration().orientation);
-
- try {
- FileLog.flushAll(writer);
- } catch (Exception e) {
- // Ignore
- }
mModel.dumpState(prefix, fd, writer, args);
@@ -2501,7 +2843,8 @@
// Setting the touch point to (-1, -1) will show the options popup in the center of
// the screen.
- OptionsPopupView.show(this, -1, -1);
+ mLastDispatchTouchEvent.set(-1, -1);
+ UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
}
return true;
}
@@ -2515,6 +2858,18 @@
return ((Launcher) ((ContextWrapper) context).getBaseContext());
}
+ private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener {
+
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
+ // Recreate the activity so that it initializes the rotation preference again.
+ recreate();
+ }
+ }
+ }
+
@Override
public void onActionModeStarted(ActionMode mode) {
super.onActionModeStarted(mode);
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 56671a1..7bc7139 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -236,7 +236,7 @@
}
@Override
- public void clearViews() {
+ protected void clearViews() {
super.clearViews();
mViews.clear();
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 80758c9..c713992 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -94,12 +94,4 @@
public boolean isCustomWidget() {
return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX);
}
-
- public int getWidgetFeatures() {
- if (Utilities.ATLEAST_P) {
- return widgetFeatures;
- } else {
- return 0;
- }
- }
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 04a32f7..6646b78 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -135,8 +135,6 @@
};
public interface Callbacks {
- public void rebindModel();
-
public int getCurrentWorkspaceScreen();
public void clearPendingBinds();
public void startBinding();
@@ -198,9 +196,8 @@
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
- public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
- return new ModelWriter(mApp.getContext(), this, sBgDataModel,
- hasVerticalHotseat, verifyChanges);
+ public ModelWriter getWriter(boolean hasVerticalHotseat) {
+ return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
}
static void checkItemInfoLocked(
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index acad901..138ea0f 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,10 +56,10 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
-import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.NoLocaleSqliteContext;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
@@ -320,11 +320,6 @@
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (ModelWriter.DEBUG_DELETE) {
- String args = selectionArgs == null ? null : TextUtils.join(",", selectionArgs);
- FileLog.d(TAG, "Delete uri=" + uri + ", selection=" + selection
- + ", selectionArgs=" + args, new Exception());
- }
createDbIfNotExists();
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
@@ -628,6 +623,10 @@
// Set the flag for empty DB
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
+
+ // When a new DB is created, remove all previously stored managed profile information.
+ ManagedProfileHeuristic.processAllUsers(Collections.<UserHandle>emptyList(),
+ mContext);
}
public long getDefaultUserSerial() {
@@ -791,7 +790,7 @@
case 23:
// No-op
case 24:
- // No-op
+ ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext);
case 25:
convertShortcutsToLauncherActivities(db);
case 26:
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 4e6bcdc..472a5a9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -20,14 +20,12 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.view.View;
import android.view.animation.Interpolator;
-import com.android.launcher3.states.SpringLoadedState;
import com.android.launcher3.uioverrides.AllAppsState;
-import com.android.launcher3.uioverrides.FastOverviewState;
+import com.android.launcher3.states.SpringLoadedState;
import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -46,11 +44,6 @@
protected static final int FLAG_DISABLE_RESTORE = 1 << 3;
protected static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = 1 << 4;
protected static final int FLAG_DISABLE_PAGE_CLIPPING = 1 << 5;
- protected static final int FLAG_PAGE_BACKGROUNDS = 1 << 6;
- protected static final int FLAG_ALL_APPS_SCRIM = 1 << 7;
- protected static final int FLAG_DISABLE_INTERACTION = 1 << 8;
- protected static final int FLAG_OVERVIEW_UI = 1 << 9;
-
protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
new PageAlphaProvider(ACCEL_2) {
@@ -60,7 +53,7 @@
}
};
- private static final LauncherState[] sAllStates = new LauncherState[5];
+ private static final LauncherState[] sAllStates = new LauncherState[4];
/**
* TODO: Create a separate class for NORMAL state.
@@ -74,8 +67,6 @@
public static final LauncherState OVERVIEW = new OverviewState(3);
- public static final LauncherState FAST_OVERVIEW = new FastOverviewState(4);
-
public final int ordinal;
/**
@@ -105,9 +96,6 @@
* @see WorkspaceStateTransitionAnimation
*/
public final boolean hasScrim;
- public final boolean hasWorkspacePageBackground;
- public final boolean hasAllAppsScrim;
-
public final int transitionDuration;
/**
@@ -121,24 +109,11 @@
*/
public final boolean disablePageClipping;
- /**
- * True if launcher can not be directly interacted in this state;
- */
- public final boolean disableInteraction;
-
- /**
- * True if the state has overview panel visible.
- */
- public final boolean overviewUi;
-
public LauncherState(int id, int containerType, int transitionDuration, int flags) {
this.containerType = containerType;
this.transitionDuration = transitionDuration;
this.hasScrim = (flags & FLAG_SHOW_SCRIM) != 0;
- this.hasWorkspacePageBackground = (flags & FLAG_PAGE_BACKGROUNDS) != 0;
- this.hasAllAppsScrim = (flags & FLAG_ALL_APPS_SCRIM) != 0;
-
this.hasMultipleVisiblePages = (flags & FLAG_MULTI_PAGE) != 0;
this.workspaceAccessibilityFlag = (flags & FLAG_DISABLE_ACCESSIBILITY) != 0
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -146,8 +121,6 @@
this.disableRestore = (flags & FLAG_DISABLE_RESTORE) != 0;
this.workspaceIconsCanBeDragged = (flags & FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED) != 0;
this.disablePageClipping = (flags & FLAG_DISABLE_PAGE_CLIPPING) != 0;
- this.disableInteraction = (flags & FLAG_DISABLE_INTERACTION) != 0;
- this.overviewUi = (flags & FLAG_OVERVIEW_UI) != 0;
this.ordinal = id;
sAllStates[id] = this;
@@ -212,8 +185,6 @@
public void onStateTransitionEnd(Launcher launcher) {
if (this == NORMAL) {
UiFactory.resetOverview(launcher);
- // Clear any rotation locks when going to normal state
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
}
}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 950a8ac..2f8687d 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -157,26 +157,18 @@
}
private void goToState(LauncherState state, boolean animated, long delay,
- final Runnable onCompleteRunnable) {
- if (mLauncher.isInState(state)) {
- if (mConfig.mCurrentAnimation == null) {
- // Run any queued runnable
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
- return;
- } else if (!mConfig.userControlled && animated) {
- // We are running the same animation as requested
- if (onCompleteRunnable != null) {
- mConfig.mCurrentAnimation.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- onCompleteRunnable.run();
- }
- });
- }
- return;
+ Runnable onCompleteRunnable) {
+ goToState(state, animated, delay, -1, onCompleteRunnable);
+ }
+
+ public void goToState(LauncherState state, boolean animated, long delay, long overrideDuration,
+ Runnable onCompleteRunnable) {
+ if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
+ // Run any queued runnable
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
}
+ return;
}
// Cancel the current animation
@@ -203,6 +195,9 @@
// Since state NORMAL can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+ if (overrideDuration > -1) {
+ mConfig.duration = overrideDuration;
+ }
AnimatorSet animation = createAnimationToNewWorkspaceInternal(
state, new AnimatorSetBuilder(), onCompleteRunnable);
@@ -325,10 +320,6 @@
}
public void moveToRestState() {
- if (mConfig.mCurrentAnimation != null && mConfig.userControlled) {
- // The user is doing something. Lets not mess it up
- return;
- }
if (mState.disableRestore) {
goToState(getRestState());
// Reset history
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 6e8cacd..d6e5d18 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -19,7 +19,10 @@
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -27,6 +30,8 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@@ -43,6 +48,7 @@
import android.view.animation.Interpolator;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.util.Thunk;
@@ -56,9 +62,7 @@
public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
private static final String TAG = "PagedView";
private static final boolean DEBUG = false;
-
protected static final int INVALID_PAGE = -1;
- protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
public static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
@@ -101,21 +105,31 @@
private VelocityTracker mVelocityTracker;
protected int mPageSpacing = 0;
+ private float mParentDownMotionX;
+ private float mParentDownMotionY;
private float mDownMotionX;
private float mDownMotionY;
+ private float mDownScrollX;
+ private float mDragViewBaselineLeft;
private float mLastMotionX;
private float mLastMotionXRemainder;
+ private float mLastMotionY;
private float mTotalMotionX;
+ private boolean mCancelTap;
+
private int[] mPageScrolls;
protected final static int TOUCH_STATE_REST = 0;
protected final static int TOUCH_STATE_SCROLLING = 1;
protected final static int TOUCH_STATE_PREV_PAGE = 2;
protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+ protected final static int TOUCH_STATE_REORDERING = 4;
protected int mTouchState = TOUCH_STATE_REST;
+ protected OnLongClickListener mLongClickListener;
+
protected int mTouchSlop;
private int mMaximumVelocity;
protected boolean mAllowOverScroll = true;
@@ -139,13 +153,33 @@
@Thunk int mPageIndicatorViewId;
protected T mPageIndicator;
+ // Reordering
+ // We use the min scale to determine how much to expand the actually PagedView measured
+ // dimensions such that when we are zoomed out, the view is not clipped
+ private static int REORDERING_DROP_REPOSITION_DURATION = 200;
+ @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
+ private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
+
+ @Thunk View mDragView;
+ private Runnable mSidePageHoverRunnable;
+ @Thunk int mSidePageHoverIndex = -1;
+ // This variable's scope is only for the duration of startReordering() and endReordering()
+ private boolean mReorderingStarted = false;
+ // This variable's scope is for the duration of startReordering() and after the zoomIn()
+ // animation after endReordering()
+ private boolean mIsReordering;
+ // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
+ private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
+ private int mPostReorderingPreZoomInRemainingAnimationCount;
+ private Runnable mPostReorderingPreZoomInRunnable;
+
// Convenience/caching
private static final Matrix sTmpInvMatrix = new Matrix();
private static final float[] sTmpPoint = new float[2];
private static final Rect sTmpRect = new Rect();
protected final Rect mInsets = new Rect();
- protected boolean mIsRtl;
+ protected final boolean mIsRtl;
// Similar to the platform implementation of isLayoutValid();
protected boolean mIsLayoutValid;
@@ -203,6 +237,47 @@
}
}
+ // Convenience methods to map points from self to parent and vice versa
+ private float[] mapPointFromViewToParent(View v, float x, float y) {
+ sTmpPoint[0] = x;
+ sTmpPoint[1] = y;
+ v.getMatrix().mapPoints(sTmpPoint);
+ sTmpPoint[0] += v.getLeft();
+ sTmpPoint[1] += v.getTop();
+ return sTmpPoint;
+ }
+ private float[] mapPointFromParentToView(View v, float x, float y) {
+ sTmpPoint[0] = x - v.getLeft();
+ sTmpPoint[1] = y - v.getTop();
+ v.getMatrix().invert(sTmpInvMatrix);
+ sTmpInvMatrix.mapPoints(sTmpPoint);
+ return sTmpPoint;
+ }
+
+ private void updateDragViewTranslationDuringDrag() {
+ if (mDragView != null) {
+ float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
+ (mDragViewBaselineLeft - mDragView.getLeft());
+ float y = mLastMotionY - mDownMotionY;
+ mDragView.setTranslationX(x);
+ mDragView.setTranslationY(y);
+
+ if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
+ + x + ", " + y);
+ }
+ }
+
+ @Override
+ public void setScaleX(float scaleX) {
+ super.setScaleX(scaleX);
+ if (isReordering(true)) {
+ float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+ mLastMotionX = p[0];
+ mLastMotionY = p[1];
+ updateDragViewTranslationDuringDrag();
+ }
+ }
+
public T getPageIndicator() {
return mPageIndicator;
}
@@ -258,7 +333,6 @@
// updating current page on the pass.
if (resetNextPage) {
mNextPage = INVALID_PAGE;
- pageEndTransition();
}
}
@@ -268,7 +342,6 @@
// updating current page on the pass.
if (resetNextPage) {
mNextPage = INVALID_PAGE;
- pageEndTransition();
}
}
@@ -308,6 +381,9 @@
// Update the page indicator (when we aren't reordering)
if (mPageIndicator != null) {
mPageIndicator.setPageDescription(getPageIndicatorDescription());
+ if (!isReordering(false)) {
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
}
}
protected void pageBeginTransition() {
@@ -343,6 +419,21 @@
mWasInOverscroll = false;
}
+ /**
+ * Registers the specified listener on each page contained in this workspace.
+ *
+ * @param l The listener used to respond to long clicks.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mLongClickListener = l;
+ final int count = getPageCount();
+ for (int i = 0; i < count; i++) {
+ getPageAt(i).setOnLongClickListener(l);
+ }
+ super.setOnLongClickListener(l);
+ }
+
protected int getUnboundedScrollX() {
return mUnboundedScrollX;
}
@@ -397,6 +488,14 @@
mOverScrollX = x;
super.scrollTo(x, y);
}
+
+ // Update the last motion events when scrolling
+ if (isReordering(true)) {
+ float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+ mLastMotionX = p[0];
+ mLastMotionY = p[1];
+ updateDragViewTranslationDuringDrag();
+ }
}
private void sendScrollAccessibilityEvent() {
@@ -448,10 +547,12 @@
pageEndTransition();
}
+ onPostReorderingAnimationCompleted();
if (isAccessibilityEnabled(getContext())) {
// Notify the user when the page changes
announceForAccessibility(getCurrentPageDescription());
}
+ return true;
}
return false;
}
@@ -542,13 +643,43 @@
if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
final int childCount = getChildCount();
+ final int startIndex = mIsRtl ? childCount - 1 : 0;
+ final int endIndex = mIsRtl ? -1 : childCount;
+ final int delta = mIsRtl ? -1 : 1;
+
+ int verticalPadding = getPaddingTop() + getPaddingBottom();
+
+ int scrollOffsetLeft = mInsets.left + getPaddingLeft();
+ int childLeft = scrollOffsetLeft;
+
boolean pageScrollChanged = false;
if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
mPageScrolls = new int[childCount];
pageScrollChanged = true;
}
- if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) {
- pageScrollChanged = true;
+
+ for (int i = startIndex; i != endIndex; i += delta) {
+ final View child = getPageAt(i);
+ if (child.getVisibility() != View.GONE) {
+ int childTop = getPaddingTop() + mInsets.top;
+ childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
+ - child.getMeasuredHeight()) / 2;
+
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(), childTop + childHeight);
+
+ final int pageScroll = childLeft - scrollOffsetLeft;
+ if (mPageScrolls[i] != pageScroll) {
+ pageScrollChanged = true;
+ mPageScrolls[i] = pageScroll;
+ }
+
+ childLeft += childWidth + mPageSpacing + getChildGap();
+ }
}
final LayoutTransition transition = getLayoutTransition();
@@ -584,51 +715,10 @@
setCurrentPage(getNextPage());
}
mChildCountOnLastLayout = childCount;
- }
- /**
- * Initializes {@code outPageScrolls} with scroll positions for view at that index. The length
- * of {@code outPageScrolls} should be same as the the childCount
- *
- */
- protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
- ComputePageScrollsLogic scrollLogic) {
- final int childCount = getChildCount();
-
- final int startIndex = mIsRtl ? childCount - 1 : 0;
- final int endIndex = mIsRtl ? -1 : childCount;
- final int delta = mIsRtl ? -1 : 1;
-
- int verticalPadding = getPaddingTop() + getPaddingBottom();
-
- int scrollOffsetLeft = mInsets.left + getPaddingLeft();
- int childLeft = scrollOffsetLeft;
- boolean pageScrollChanged = false;
-
- for (int i = startIndex; i != endIndex; i += delta) {
- final View child = getPageAt(i);
- if (scrollLogic.shouldIncludeView(child)) {
- int childTop = getPaddingTop() + mInsets.top;
- childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
- - child.getMeasuredHeight()) / 2;
- final int childWidth = child.getMeasuredWidth();
-
- if (layoutChildren) {
- final int childHeight = child.getMeasuredHeight();
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(), childTop + childHeight);
- }
-
- final int pageScroll = childLeft - scrollOffsetLeft;
- if (outPageScrolls[i] != pageScroll) {
- pageScrollChanged = true;
- outPageScrolls[i] = pageScroll;
- }
-
- childLeft += childWidth + mPageSpacing + getChildGap();
- }
+ if (isReordering(true)) {
+ updateDragViewTranslationDuringDrag();
}
- return pageScrollChanged;
}
protected int getChildGap() {
@@ -665,13 +755,11 @@
@Override
public void onViewAdded(View child) {
- super.onViewAdded(child);
dispatchPageCountChanged();
}
@Override
public void onViewRemoved(View child) {
- super.onViewRemoved(child);
mCurrentPage = validateNewPage(mCurrentPage);
dispatchPageCountChanged();
}
@@ -848,7 +936,12 @@
// Remember location of down touch
mDownMotionX = x;
mDownMotionY = y;
+ mDownScrollX = getScrollX();
mLastMotionX = x;
+ mLastMotionY = y;
+ float[] p = mapPointFromViewToParent(this, x, y);
+ mParentDownMotionX = p[0];
+ mParentDownMotionY = p[1];
mLastMotionXRemainder = 0;
mTotalMotionX = 0;
mActivePointerId = ev.getPointerId(0);
@@ -896,10 +989,6 @@
return mTouchState != TOUCH_STATE_REST;
}
- public boolean isHandlingTouch() {
- return mTouchState != TOUCH_STATE_REST;
- }
-
protected void determineScrollingStart(MotionEvent ev) {
determineScrollingStart(ev, 1.0f);
}
@@ -1011,12 +1100,22 @@
dampedOverScroll(amount);
}
+ /**
+ * return true if freescroll has been enabled, false otherwise
+ */
+ protected void enableFreeScroll() {
+ enableFreeScroll(false);
+ }
protected void enableFreeScroll(boolean settleOnPageInFreeScroll) {
setEnableFreeScroll(true);
mSettleOnPageInFreeScroll = settleOnPageInFreeScroll;
}
+ protected void disableFreeScroll() {
+ setEnableFreeScroll(false);
+ }
+
private void setEnableFreeScroll(boolean freeScroll) {
boolean wasFreeScroll = mFreeScroll;
mFreeScroll = freeScroll;
@@ -1034,6 +1133,27 @@
mAllowOverScroll = enable;
}
+ private int getNearestHoverOverPageIndex() {
+ if (mDragView != null) {
+ int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
+ + mDragView.getTranslationX());
+ int minDistance = Integer.MAX_VALUE;
+ int minIndex = indexOfChild(mDragView);
+ int maxPageNo = getChildCount() - 1;
+ for (int i = 0; i <= maxPageNo; i++) {
+ View page = getPageAt(i);
+ int pageX = (page.getLeft() + page.getMeasuredWidth() / 2);
+ int d = Math.abs(dragX - pageX);
+ if (d < minDistance) {
+ minIndex = i;
+ minDistance = d;
+ }
+ }
+ return minIndex;
+ }
+ return -1;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
@@ -1057,7 +1177,11 @@
// Remember where the motion event started
mDownMotionX = mLastMotionX = ev.getX();
- mDownMotionY = ev.getY();
+ mDownMotionY = mLastMotionY = ev.getY();
+ mDownScrollX = getScrollX();
+ float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = p[0];
+ mParentDownMotionY = p[1];
mLastMotionXRemainder = 0;
mTotalMotionX = 0;
mActivePointerId = ev.getPointerId(0);
@@ -1090,6 +1214,82 @@
} else {
awakenScrollBars();
}
+ } else if (mTouchState == TOUCH_STATE_REORDERING) {
+ // Update the last motion position
+ mLastMotionX = ev.getX();
+ mLastMotionY = ev.getY();
+
+ // Update the parent down so that our zoom animations take this new movement into
+ // account
+ float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = pt[0];
+ mParentDownMotionY = pt[1];
+ updateDragViewTranslationDuringDrag();
+
+ // Find the closest page to the touch point
+ final int dragViewIndex = indexOfChild(mDragView);
+
+ if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
+ if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
+ if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
+ if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
+
+ final int pageUnderPointIndex = getNearestHoverOverPageIndex();
+ // Do not allow any page to be moved to 0th position.
+ if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
+ if (0 <= pageUnderPointIndex && pageUnderPointIndex <= getPageCount() - 1 &&
+ pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
+ mSidePageHoverIndex = pageUnderPointIndex;
+ mSidePageHoverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Setup the scroll to the correct page before we swap the views
+ snapToPage(pageUnderPointIndex);
+
+ // For each of the pages between the paged view and the drag view,
+ // animate them from the previous position to the new position in
+ // the layout (as a result of the drag view moving in the layout)
+ int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
+ int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
+ dragViewIndex + 1 : pageUnderPointIndex;
+ int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
+ dragViewIndex - 1 : pageUnderPointIndex;
+ for (int i = lowerIndex; i <= upperIndex; ++i) {
+ View v = getChildAt(i);
+ // dragViewIndex < pageUnderPointIndex, so after we remove the
+ // drag view all subsequent views to pageUnderPointIndex will
+ // shift down.
+ int oldX = getChildOffset(i);
+ int newX = getChildOffset(i + shiftDelta);
+
+ // Animate the view translation from its old position to its new
+ // position
+ ObjectAnimator anim = (ObjectAnimator) v.getTag();
+ if (anim != null) {
+ anim.cancel();
+ }
+
+ v.setTranslationX(oldX - newX);
+ anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0);
+ anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
+ anim.start();
+ v.setTag(anim);
+ }
+
+ removeView(mDragView);
+ addView(mDragView, pageUnderPointIndex);
+ mSidePageHoverIndex = -1;
+ if (mPageIndicator != null) {
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
+ }
+ };
+ postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
+ }
+ } else {
+ removeCallbacks(mSidePageHoverRunnable);
+ mSidePageHoverIndex = -1;
+ }
} else {
determineScrollingStart(ev);
}
@@ -1190,8 +1390,25 @@
} else {
snapToDestination();
}
+ } else if (mTouchState == TOUCH_STATE_REORDERING) {
+ // Update the last motion position
+ mLastMotionX = ev.getX();
+ mLastMotionY = ev.getY();
+
+ // Update the parent down so that our zoom animations take this new movement into
+ // account
+ float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = pt[0];
+ mParentDownMotionY = pt[1];
+ updateDragViewTranslationDuringDrag();
+ } else {
+ if (!mCancelTap) {
+ onUnhandledTap(ev);
+ }
}
+ // Remove the callback to wait for the side page hover timeout
+ removeCallbacks(mSidePageHoverRunnable);
// End any intermediate reordering states
resetTouchState();
break;
@@ -1219,6 +1436,8 @@
private void resetTouchState() {
releaseVelocityTracker();
+ endReordering();
+ mCancelTap = false;
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
}
@@ -1232,6 +1451,10 @@
protected void onScrollInteractionEnd() {
}
+ protected void onUnhandledTap(MotionEvent ev) {
+ Launcher.getLauncher(getContext()).onClick(this);
+ }
+
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -1288,6 +1511,7 @@
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+ mLastMotionY = ev.getY(newPointerIndex);
mLastMotionXRemainder = 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
@@ -1464,6 +1688,139 @@
if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
}
+ @Override
+ public boolean performLongClick() {
+ mCancelTap = true;
+ return super.performLongClick();
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int currentPage = -1;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Thunk SavedState(Parcel in) {
+ super(in);
+ currentPage = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(currentPage);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ // Animate the drag view back to the original position
+ private void animateDragViewToOriginalPosition() {
+ if (mDragView != null) {
+ Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
+ new PropertyListBuilder()
+ .scale(1)
+ .translationX(0)
+ .translationY(0)
+ .build())
+ .setDuration(REORDERING_DROP_REPOSITION_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onPostReorderingAnimationCompleted();
+ }
+ });
+ anim.start();
+ }
+ }
+
+ public void onStartReordering() {
+ // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
+ mTouchState = TOUCH_STATE_REORDERING;
+ mIsReordering = true;
+
+ // We must invalidate to trigger a redraw to update the layers such that the drag view
+ // is always drawn on top
+ invalidate();
+ }
+
+ @Thunk void onPostReorderingAnimationCompleted() {
+ // Trigger the callback when reordering has settled
+ --mPostReorderingPreZoomInRemainingAnimationCount;
+ if (mPostReorderingPreZoomInRunnable != null &&
+ mPostReorderingPreZoomInRemainingAnimationCount == 0) {
+ mPostReorderingPreZoomInRunnable.run();
+ mPostReorderingPreZoomInRunnable = null;
+ }
+ }
+
+ public void onEndReordering() {
+ mIsReordering = false;
+ }
+
+ public boolean startReordering(View v) {
+ int dragViewIndex = indexOfChild(v);
+
+ // Do not allow the first page to be moved around
+ if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
+
+ // Check if we are within the reordering range
+ if (0 <= dragViewIndex && dragViewIndex <= getPageCount() - 1) {
+ // Find the drag view under the pointer
+ mDragView = getChildAt(dragViewIndex);
+ mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
+ mDragViewBaselineLeft = mDragView.getLeft();
+ mReorderingStarted = true;
+
+ snapToPage(getPageNearestToCenterOfScreen());
+ disableFreeScroll();
+ onStartReordering();
+ return true;
+ }
+ return false;
+ }
+
+ boolean isReordering(boolean testTouchState) {
+ boolean state = mIsReordering;
+ if (testTouchState) {
+ state &= (mTouchState == TOUCH_STATE_REORDERING);
+ }
+ return state;
+ }
+ void endReordering() {
+ // For simplicity, we call endReordering sometimes even if reordering was never started.
+ // In that case, we don't want to do anything.
+ if (!mReorderingStarted) return;
+ mReorderingStarted = false;
+
+ mPostReorderingPreZoomInRunnable = new Runnable() {
+ public void run() {
+ // If we haven't flung-to-delete the current child,
+ // then we just animate the drag view back into position
+ onEndReordering();
+
+ enableFreeScroll();
+ }
+ };
+
+ mPostReorderingPreZoomInRemainingAnimationCount =
+ NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
+ // Snap to the current page
+ snapToPage(indexOfChild(mDragView), 0);
+ // Animate the drag view back to the front position
+ animateDragViewToOriginalPosition();
+ }
+
/* Accessibility */
@SuppressWarnings("deprecation")
@Override
@@ -1542,9 +1899,4 @@
public boolean onHoverEvent(android.view.MotionEvent event) {
return true;
}
-
- protected interface ComputePageScrollsLogic {
-
- boolean shouldIncludeView(View view);
- }
}
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index b0da6b9..edb7ff5 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -67,9 +67,11 @@
SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- if (TextUtils.isEmpty(info.getAppPackageName()) ||
- info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
- return;
+ if (Process.myUserHandle().equals(user)) {
+ if (TextUtils.isEmpty(info.getAppPackageName()) ||
+ info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+ return;
+ }
}
queueAppIconAddition(context, info.getAppPackageName(), user);
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 6a4e93b..d40ac8f 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -65,6 +65,7 @@
*/
public static class LauncherSettingsFragment extends PreferenceFragment {
+ private SystemDisplayRotationLockObserver mRotationLockObserver;
private IconBadgingObserver mIconBadgingObserver;
@Override
@@ -75,6 +76,22 @@
ContentResolver resolver = getActivity().getContentResolver();
+ // Setup allow rotation preference
+ Preference rotationPref = findPreference(Utilities.ALLOW_ROTATION_PREFERENCE_KEY);
+ if (getResources().getBoolean(R.bool.allow_rotation)) {
+ // Launcher supports rotation by default. No need to show this setting.
+ getPreferenceScreen().removePreference(rotationPref);
+ } else {
+ mRotationLockObserver = new SystemDisplayRotationLockObserver(rotationPref, resolver);
+
+ // Register a content observer to listen for system setting changes while
+ // this UI is active.
+ mRotationLockObserver.register(Settings.System.ACCELEROMETER_ROTATION);
+
+ // Initialize the UI once
+ rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
+ }
+
ButtonPreference iconBadgingPref =
(ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
if (!Utilities.ATLEAST_OREO) {
@@ -102,6 +119,10 @@
@Override
public void onDestroy() {
+ if (mRotationLockObserver != null) {
+ mRotationLockObserver.unregister();
+ mRotationLockObserver = null;
+ }
if (mIconBadgingObserver != null) {
mIconBadgingObserver.unregister();
mIconBadgingObserver = null;
@@ -111,6 +132,28 @@
}
/**
+ * Content observer which listens for system auto-rotate setting changes, and enables/disables
+ * the launcher rotation setting accordingly.
+ */
+ private static class SystemDisplayRotationLockObserver extends SettingsObserver.System {
+
+ private final Preference mRotationPref;
+
+ public SystemDisplayRotationLockObserver(
+ Preference rotationPref, ContentResolver resolver) {
+ super(resolver);
+ mRotationPref = rotationPref;
+ }
+
+ @Override
+ public void onSettingChanged(boolean enabled) {
+ mRotationPref.setEnabled(enabled);
+ mRotationPref.setSummary(enabled
+ ? R.string.allow_rotation_desc : R.string.allow_rotation_blocked_desc);
+ }
+ }
+
+ /**
* Content observer which listens for system badging setting changes,
* and updates the launcher badging setting subtext accordingly.
*/
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 8588c7a..ec608ca 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -79,7 +79,7 @@
* A message to display when the user tries to start a disabled shortcut.
* This is currently only used for deep shortcuts.
*/
- public CharSequence disabledMessage;
+ CharSequence disabledMessage;
public int status;
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
similarity index 61%
rename from src/com/android/launcher3/SecondaryDropTarget.java
rename to src/com/android/launcher3/UninstallDropTarget.java
index 024b4eb..68a441a 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,19 +1,8 @@
package com.android.launcher3;
-import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
-
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.SETTINGS_BUTTON;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNINSTALL_TARGET;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -31,33 +20,27 @@
import android.widget.Toast;
import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.Themes;
import java.net.URISyntaxException;
-/**
- * Drop target which provides a secondary option for an item.
- * For app targets: shows as uninstall
- * For configurable widgets: shows as setup
- */
-public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
+public class UninstallDropTarget extends ButtonDropTarget implements OnAlarmListener {
- private static final String TAG = "SecondaryDropTarget";
+ private static final String TAG = "UninstallDropTarget";
private static final long CACHE_EXPIRE_TIMEOUT = 5000;
private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
private final Alarm mCacheExpireAlarm;
- private int mCurrentAccessibilityAction = -1;
- public SecondaryDropTarget(Context context, AttributeSet attrs) {
+ public UninstallDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mCacheExpireAlarm = new Alarm();
@@ -67,24 +50,13 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- setupUi(UNINSTALL);
+ setupUi();
}
- private void setupUi(int action) {
- if (action == mCurrentAccessibilityAction) {
- return;
- }
- mCurrentAccessibilityAction = action;
-
- if (action == UNINSTALL) {
- mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
- setDrawable(R.drawable.ic_uninstall_shadow);
- updateText(R.string.uninstall_drop_target_label);
- } else {
- mHoverColor = Themes.getColorAccent(getContext());
- setDrawable(R.drawable.ic_setup_shadow);
- updateText(R.string.gadget_setup_text);
- }
+ protected void setupUi() {
+ // Get the hover color
+ mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
+ setDrawable(R.drawable.ic_uninstall_shadow);
}
@Override
@@ -94,30 +66,11 @@
@Override
public int getAccessibilityAction() {
- return mCurrentAccessibilityAction;
- }
-
- @Override
- public int getControlTypeForLogging() {
- return mCurrentAccessibilityAction == UNINSTALL ? UNINSTALL_TARGET : SETTINGS_BUTTON;
+ return LauncherAccessibilityDelegate.UNINSTALL;
}
@Override
protected boolean supportsDrop(ItemInfo info) {
- return supportsAccessibilityDrop(info, getViewUnderDrag(info));
- }
-
- @Override
- public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
- if (view instanceof AppWidgetHostView) {
- if (getReconfigurableWidgetId(view) != INVALID_APPWIDGET_ID) {
- setupUi(RECONFIGURE);
- return true;
- }
- return false;
- }
-
- setupUi(UNINSTALL);
Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
if (uninstallDisabled == null) {
UserManager userManager =
@@ -173,7 +126,7 @@
@Override
public void completeDrop(final DragObject d) {
- ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);
+ ComponentName target = performDropAction(d.dragInfo);
if (d.dragSource instanceof DeferredOnComplete) {
DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
if (target != null) {
@@ -185,48 +138,11 @@
}
}
- private View getViewUnderDrag(ItemInfo info) {
- if (info instanceof LauncherAppWidgetInfo && info.container == CONTAINER_DESKTOP &&
- mLauncher.getWorkspace().getDragInfo() != null) {
- return mLauncher.getWorkspace().getDragInfo().cell;
- }
- return null;
- }
-
- /**
- * Verifies that the view is an reconfigurable widget and returns the corresponding widget Id,
- * otherwise return {@code INVALID_APPWIDGET_ID}
- */
- private int getReconfigurableWidgetId(View view) {
- if (!(view instanceof AppWidgetHostView)) {
- return INVALID_APPWIDGET_ID;
- }
- AppWidgetHostView hostView = (AppWidgetHostView) view;
- AppWidgetProviderInfo widgetInfo = hostView.getAppWidgetInfo();
- if (widgetInfo == null || widgetInfo.configure == null) {
- return INVALID_APPWIDGET_ID;
- }
- if ( (LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(), widgetInfo)
- .getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) == 0) {
- return INVALID_APPWIDGET_ID;
- }
- return hostView.getAppWidgetId();
- }
-
/**
* Performs the drop action and returns the target component for the dragObject or null if
* the action was not performed.
*/
- protected ComponentName performDropAction(View view, ItemInfo info) {
- if (mCurrentAccessibilityAction == RECONFIGURE) {
- int widgetId = getReconfigurableWidgetId(view);
- if (widgetId != INVALID_APPWIDGET_ID) {
- mLauncher.getAppWidgetHost().startConfigActivity(mLauncher, widgetId, -1);
- }
- return null;
- }
- // else: mCurrentAccessibilityAction == UNINSTALL
-
+ protected ComponentName performDropAction(ItemInfo info) {
ComponentName cn = getUninstallTarget(info);
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
@@ -248,7 +164,7 @@
@Override
public void onAccessibilityDrop(View view, ItemInfo item) {
- performDropAction(view, item);
+ performDropAction(item);
}
/**
@@ -287,7 +203,7 @@
.getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
mDragObject.dragInfo.user) == null) {
mDragObject.dragSource = mOriginal;
- mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
+ mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, true);
} else {
sendFailure();
}
@@ -296,7 +212,7 @@
public void sendFailure() {
mDragObject.dragSource = mOriginal;
mDragObject.cancelled = true;
- mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, false);
+ mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, false);
}
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cabccbf..31dab97 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -125,10 +125,29 @@
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+ public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
+
public static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
+ public static boolean isAllowRotationPrefEnabled(Context context) {
+ return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ getAllowRotationDefaultValue(context));
+ }
+
+ public static boolean getAllowRotationDefaultValue(Context context) {
+ if (ATLEAST_NOUGAT) {
+ // If the device was scaled, used the original dimensions to determine if rotation
+ // is allowed of not.
+ Resources res = context.getResources();
+ int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+ * res.getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEVICE_STABLE;
+ return originalSmallestWidth >= 600;
+ }
+ return false;
+ }
+
/**
* Given a coordinate relative to the descendant, find the coordinate in a parent view's
* coordinates.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9960953..1414946 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -59,6 +59,7 @@
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
@@ -75,13 +76,9 @@
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.graphics.ViewScrim;
-import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -242,6 +239,7 @@
private Runnable mOnOverlayHiddenCallback;
private boolean mForceDrawAdjacentPages = false;
+ private boolean mPageRearrangeEnabled = false;
// Total over scrollX in the overlay direction.
private float mOverlayTranslation;
@@ -249,6 +247,8 @@
// Handles workspace state transitions
private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
+ private AccessibilityDelegate mPagesAccessibilityDelegate;
+
/**
* Used to inflate the Workspace from XML.
*
@@ -280,10 +280,6 @@
// Disable multitouch across the workspace/all apps/customize tray
setMotionEventSplittingEnabled(true);
-
- // Attach a scrim
- new WorkspaceAndHotseatScrim(this).attach();
- setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
}
@Override
@@ -473,6 +469,7 @@
}
CellLayout cl = ((CellLayout) child);
cl.setOnInterceptTouchListener(this);
+ cl.setClickable(true);
cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
super.onViewAdded(child);
}
@@ -552,6 +549,10 @@
// created CellLayout.
CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
+ newScreen.setOnLongClickListener(mLongClickListener);
+ newScreen.setOnClickListener(mLauncher);
+ newScreen.setSoundEffectsEnabled(false);
+
int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -929,8 +930,10 @@
Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
}
- child.setHapticFeedbackEnabled(false);
- child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+ if (!(child instanceof Folder)) {
+ child.setHapticFeedbackEnabled(false);
+ child.setOnLongClickListener(mLongClickListener);
+ }
if (child instanceof DropTarget) {
mDragController.addDropTarget((DropTarget) child);
}
@@ -1370,7 +1373,8 @@
}
private void updateChildrenLayersEnabled() {
- boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
+ boolean enableChildrenLayers =
+ isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition();
if (enableChildrenLayers != mChildrenLayersEnabled) {
mChildrenLayersEnabled = enableChildrenLayers;
@@ -1454,6 +1458,40 @@
mOutlineProvider = outlineProvider;
}
+ public void onStartReordering() {
+ super.onStartReordering();
+ // Reordering handles its own animations, disable the automatic ones.
+ disableLayoutTransitions();
+ }
+
+ public void onEndReordering() {
+ super.onEndReordering();
+
+ if (mLauncher.isWorkspaceLoading()) {
+ // Invalid and dangerous operation if workspace is loading
+ return;
+ }
+
+ ArrayList<Long> prevScreenOrder = (ArrayList<Long>) mScreenOrder.clone();
+ mScreenOrder.clear();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ CellLayout cl = ((CellLayout) getChildAt(i));
+ mScreenOrder.add(getIdForScreen(cl));
+ }
+
+ for (int i = 0; i < prevScreenOrder.size(); i++) {
+ if (mScreenOrder.get(i) != prevScreenOrder.get(i)) {
+ mLauncher.getUserEventDispatcher().logOverviewReorder();
+ break;
+ }
+ }
+ LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+
+ // Re-enable auto layout transitions for page deletion.
+ enableLayoutTransitions();
+ }
+
public void snapToPageFromOverView(int whichPage) {
snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
}
@@ -1513,17 +1551,47 @@
if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
int total = getPageCount();
for (int i = 0; i < total; i++) {
- updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
+ updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i);
}
setImportantForAccessibility(accessibilityFlag);
}
}
- private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
- page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
- page.setContentDescription(null);
- page.setAccessibilityDelegate(null);
+ private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page, int pageNo) {
+ if (isPageRearrangeEnabled()) {
+ page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ page.getShortcutsAndWidgets().setImportantForAccessibility(
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ page.setContentDescription(getPageDescription(pageNo));
+
+ // No custom action for the first page.
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) {
+ if (mPagesAccessibilityDelegate == null) {
+ mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+ }
+ page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
+ }
+ } else {
+ page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
+ page.setContentDescription(null);
+ page.setAccessibilityDelegate(null);
+ }
+ }
+
+ public void setPageRearrangeEnabled(boolean isEnabled) {
+ if (mPageRearrangeEnabled != isEnabled) {
+ mPageRearrangeEnabled = isEnabled;
+ if (isEnabled) {
+ enableFreeScroll();
+ } else {
+ disableFreeScroll();
+ }
+ }
+ }
+
+ public boolean isPageRearrangeEnabled() {
+ return mPageRearrangeEnabled;
}
public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -1539,6 +1607,10 @@
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
+
+ // We need to allow our individual children to become click handlers in this
+ // case, so temporarily unset the click handlers.
+ setOnClickListener(enable ? null : mLauncher);
}
});
}
@@ -1558,6 +1630,7 @@
new DragPreviewProvider(child), options);
}
+
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
DragPreviewProvider previewProvider, DragOptions dragOptions) {
child.clearFocus();
@@ -2156,7 +2229,7 @@
}
// Invalidating the scrim will also force this CellLayout
// to be invalidated so that it is highlighted if necessary.
- ViewScrim.get(this).invalidate();
+ mLauncher.getDragLayer().invalidateScrim();
}
public CellLayout getCurrentDragOverlappingLayout() {
@@ -2892,9 +2965,8 @@
+ "Workspace#onDropCompleted. Please file a bug. ");
}
}
- View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
- if (d.cancelled && cell != null) {
- cell.setVisibility(VISIBLE);
+ if (d.cancelled && mDragInfo != null && mDragInfo.cell != null) {
+ mDragInfo.cell.setVisibility(VISIBLE);
}
mDragInfo = null;
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index f6d0248..21f5d67 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -32,7 +32,6 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.ViewScrim;
/**
* A convenience class to update a view's visibility state after an alpha animation.
@@ -91,6 +90,8 @@
public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
+ public final int mWorkspaceScrimAlpha;
+
private final Launcher mLauncher;
private final Workspace mWorkspace;
@@ -99,6 +100,8 @@
public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
mLauncher = launcher;
mWorkspace = workspace;
+ mWorkspaceScrimAlpha = launcher.getResources()
+ .getInteger(R.integer.config_workspaceScrimAlpha);
}
public void setState(LauncherState toState) {
@@ -137,14 +140,10 @@
propertySetter.setViewAlpha(mLauncher.getHotseat(), state.getHoseatAlpha(mLauncher),
pageAlphaProvider.interpolator);
- propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- state.getHoseatAlpha(mLauncher), pageAlphaProvider.interpolator);
// Set scrim
- propertySetter.setFloat(ViewScrim.get(mWorkspace), ViewScrim.PROGRESS,
- state.hasScrim ? 1 : 0, Interpolators.LINEAR);
- propertySetter.setFloat(ViewScrim.get(mLauncher.getAppsView()), ViewScrim.PROGRESS,
- state.hasAllAppsScrim ? 1 : 0, Interpolators.LINEAR);
+ propertySetter.setInt(mLauncher.getDragLayer().getScrim(), DRAWABLE_ALPHA,
+ state.hasScrim ? mWorkspaceScrimAlpha : 0, Interpolators.DEACCEL_1_5);
}
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
@@ -155,7 +154,7 @@
private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
- int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
+ int drawableAlpha = Math.round(pageAlpha * (state.hasScrim ? 255 : 0));
propertySetter.setInt(cl.getScrimBackground(),
DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_IN);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 4398f6e..3b6fea9 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -25,7 +25,6 @@
import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
@@ -48,8 +47,8 @@
private static final String TAG = "LauncherAccessibilityDelegate";
public static final int REMOVE = R.id.action_remove;
+ public static final int INFO = R.id.action_info;
public static final int UNINSTALL = R.id.action_uninstall;
- public static final int RECONFIGURE = R.id.action_reconfigure;
protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
protected static final int MOVE = R.id.action_move;
protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
@@ -78,10 +77,10 @@
mActions.put(REMOVE, new AccessibilityAction(REMOVE,
launcher.getText(R.string.remove_drop_target_label)));
+ mActions.put(INFO, new AccessibilityAction(INFO,
+ launcher.getText(R.string.app_info_drop_target_label)));
mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
launcher.getText(R.string.uninstall_drop_target_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,
@@ -111,7 +110,7 @@
}
for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
- if (target.supportsAccessibilityDrop(item, host)) {
+ if (target.supportsAccessibilityDrop(item)) {
info.addAction(mActions.get(target.getAccessibilityAction()));
}
}
@@ -223,8 +222,7 @@
return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
} else {
for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
- if (dropTarget.supportsAccessibilityDrop(item, host) &&
- action == dropTarget.getAccessibilityAction()) {
+ if (action == dropTarget.getAccessibilityAction()) {
dropTarget.onAccessibilityDrop(host, item);
return true;
}
@@ -359,14 +357,29 @@
mDragInfo.dragType = DragType.WIDGET;
}
+ CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
+
Rect pos = new Rect();
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
+
+ Folder folder = Folder.getOpen(mLauncher);
+ if (folder != null) {
+ if (!folder.getItemsInReadingOrder().contains(item)) {
+ folder.close(true);
+ folder = null;
+ }
+ }
+
mLauncher.getDragController().addDragListener(this);
DragOptions options = new DragOptions();
options.isAccessibleDrag = true;
- ItemLongClickListener.beginDrag(item, mLauncher, info, options);
+ if (folder != null) {
+ folder.startDrag(cellInfo.cell, options);
+ } else {
+ mLauncher.getWorkspace().startDrag(cellInfo, options);
+ }
}
@Override
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
new file mode 100644
index 0000000..f9eb2ed
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.accessibility;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
+
+public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
+
+ private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
+ private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
+
+ private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+ private final Workspace mWorkspace;
+
+ public OverviewScreenAccessibilityDelegate(Workspace workspace) {
+ mWorkspace = workspace;
+
+ Context context = mWorkspace.getContext();
+ boolean isRtl = Utilities.isRtl(context.getResources());
+ mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
+ context.getText(isRtl ? R.string.action_move_screen_right :
+ R.string.action_move_screen_left)));
+ mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
+ context.getText(isRtl ? R.string.action_move_screen_left :
+ R.string.action_move_screen_right)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (host != null) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
+ int index = mWorkspace.indexOfChild(host);
+ mWorkspace.setCurrentPage(index);
+ } else if (action == MOVE_FORWARD) {
+ movePage(mWorkspace.indexOfChild(host) + 1, host);
+ return true;
+ } else if (action == MOVE_BACKWARD) {
+ movePage(mWorkspace.indexOfChild(host) - 1, host);
+ return true;
+ }
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private void movePage(int finalIndex, View view) {
+ mWorkspace.onStartReordering();
+ mWorkspace.removeView(view);
+ mWorkspace.addView(view, finalIndex);
+ mWorkspace.onEndReordering();
+ mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
+
+ mWorkspace.updateAccessibilityFlags();
+ view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ int index = mWorkspace.indexOfChild(host);
+ if (index < mWorkspace.getChildCount() - 1) {
+ info.addAction(mActions.get(MOVE_FORWARD));
+ }
+
+ int startIndex = FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0;
+ if (index > startIndex) {
+ info.addAction(mActions.get(MOVE_BACKWARD));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 39a8df3..a7eae39 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,17 +15,12 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Process;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.StringRes;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
@@ -35,43 +30,47 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.widget.RelativeLayout;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
+import com.android.launcher3.ClickShadowView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BottomUserEducationView;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.SpringRelativeLayout;
/**
* The all apps view container.
*/
-public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
- Insettable, OnDeviceProfileChangeListener {
+public class AllAppsContainerView extends RelativeLayout implements DragSource,
+ OnLongClickListener, Insettable, BubbleTextShadowHandler, OnDeviceProfileChangeListener {
private final Launcher mLauncher;
private final AdapterHolder[] mAH;
+ private final ClickShadowView mTouchFeedbackView;
private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
private final AllAppsStore mAllAppsStore = new AllAppsStore();
- private final Paint mNavBarScrimPaint;
- private int mNavBarScrimHeight = 0;
-
private SearchUiManager mSearchUiManager;
private View mSearchContainer;
private AllAppsPagedView mViewPager;
@@ -82,9 +81,6 @@
private boolean mUsingTabs;
private boolean mSearchModeWhileUsingTabs = false;
- private RecyclerViewFastScroller mTouchHandler;
- private final Point mFastScrollerOffset = new Point();
-
public AllAppsContainerView(Context context) {
this(context, null);
}
@@ -100,24 +96,30 @@
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchQueryBuilder = new SpannableStringBuilder();
+
Selection.setSelection(mSearchQueryBuilder, 0);
+ mTouchFeedbackView = new ClickShadowView(context);
+ // Make the feedback view large enough to hold the blur bitmap.
+ int size = mLauncher.getDeviceProfile().allAppsIconSizePx
+ + mTouchFeedbackView.getExtraSize();
+ addView(mTouchFeedbackView, size, size);
+
mAH = new AdapterHolder[2];
mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
- mNavBarScrimPaint = new Paint();
- mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
-
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
+ }
- // Attach a scrim to be drawn behind all-apps and hotseat
- new ColorScrim(this, Themes.getAttrColor(context, R.attr.allAppsScrimColor), DEACCEL_2)
- .attach();
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ applyTouchDelegate();
+ }
- addSpringView(R.id.all_apps_header);
- addSpringView(R.id.apps_list_view);
- addSpringView(R.id.all_apps_tabs_view_pager);
+ private void applyTouchDelegate() {
+ // TODO: Reimplement once fast scroller is fixed.
}
public AllAppsStore getAppsStore() {
@@ -125,6 +127,12 @@
}
@Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ applyTouchDelegate();
+ }
+
+ @Override
public void onDeviceProfileChanged(DeviceProfile dp) {
for (AdapterHolder holder : mAH) {
if (holder.recyclerView != null) {
@@ -149,6 +157,11 @@
}
}
+ @Override
+ public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+ mTouchFeedbackView.setPressedIcon(icon, background);
+ }
+
/**
* Returns whether the view itself will handle the touch event or not.
*/
@@ -159,51 +172,7 @@
return true;
}
AllAppsRecyclerView rv = getActiveRecyclerView();
- if (rv == null) {
- return true;
- }
- if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
- mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
- return false;
- }
- return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- AllAppsRecyclerView rv = getActiveRecyclerView();
- if (rv != null &&
- rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
- mTouchHandler = rv.getScrollbar();
- }
- }
- if (mTouchHandler != null) {
- return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
- }
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mTouchHandler != null) {
- mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
- return true;
- }
- return false;
- }
-
- public String getDescription() {
- @StringRes int descriptionRes;
- if (mUsingTabs) {
- descriptionRes =
- mViewPager.getNextPage() == 0
- ? R.string.all_apps_button_personal_label
- : R.string.all_apps_button_work_label;
- } else {
- descriptionRes = R.string.all_apps_button_label;
- }
- return getContext().getString(descriptionRes);
+ return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
}
public AllAppsRecyclerView getActiveRecyclerView() {
@@ -227,7 +196,7 @@
mHeader.reset();
}
// Reset the search bar and base recycler view after transitioning home
- mSearchUiManager.resetSearch();
+ mSearchUiManager.reset();
}
@Override
@@ -261,6 +230,37 @@
}
@Override
+ public boolean onLongClick(final View v) {
+ // When we have exited all apps or are in transition, disregard long clicks
+ if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
+ mLauncher.getWorkspace().isSwitchingState()) return false;
+ // Return if global dragging is not enabled or we are already dragging
+ if (!mLauncher.isDraggingEnabled()) return false;
+ if (mLauncher.getDragController().isDragging()) return false;
+
+ // Start the drag
+ final DragController dragController = mLauncher.getDragController();
+ dragController.addDragListener(new DragController.DragListener() {
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ v.setVisibility(INVISIBLE);
+ }
+
+ @Override
+ public void onDragEnd() {
+ v.setVisibility(VISIBLE);
+ dragController.removeDragListener(this);
+ }
+ });
+
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ DragOptions options = new DragOptions();
+ options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
+ mLauncher.getWorkspace().beginDragShared(v, this, options);
+ return false;
+ }
+
+ @Override
public void onDropCompleted(View target, DragObject d, boolean success) { }
@Override
@@ -283,26 +283,25 @@
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
if (grid.isVerticalBarLayout()) {
mlp.leftMargin = insets.left;
+ mlp.topMargin = insets.top;
mlp.rightMargin = insets.right;
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
- mlp.leftMargin = mlp.rightMargin = 0;
+ mlp.leftMargin = mlp.rightMargin = mlp.topMargin = 0;
setPadding(0, 0, 0, 0);
}
setLayoutParams(mlp);
- mNavBarScrimHeight = insets.bottom;
+ View navBarBg = findViewById(R.id.nav_bar_bg);
+ ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
+ navBarBgLp.height = insets.bottom;
+ navBarBg.setLayoutParams(navBarBgLp);
+
InsettableFrameLayout.dispatchInsets(this, insets);
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- if (mNavBarScrimHeight > 0) {
- canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
- mNavBarScrimPaint);
- }
+ public SpringAnimationHandler getSpringAnimationHandler() {
+ return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
}
private void rebindAdapters(boolean showTabs) {
@@ -331,6 +330,8 @@
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
+
+ applyTouchDelegate();
}
private void replaceRVContainer(boolean showTabs) {
@@ -361,6 +362,7 @@
public void onTabChanged(int pos) {
mHeader.setMainActive(pos == 0);
reset();
+ applyTouchDelegate();
if (mAH[pos].recyclerView != null) {
mAH[pos].recyclerView.bindFastScrollbar();
@@ -447,6 +449,7 @@
public final AllAppsGridAdapter adapter;
final LinearLayoutManager layoutManager;
+ final SpringAnimationHandler animationHandler;
final AlphabeticalAppsList appsList;
final Rect padding = new Rect();
AllAppsRecyclerView recyclerView;
@@ -454,21 +457,25 @@
AdapterHolder(boolean isWork) {
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
- adapter = new AllAppsGridAdapter(mLauncher, appsList);
+ adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
+ AllAppsContainerView.this, true);
appsList.setAdapter(adapter);
+ animationHandler = adapter.getSpringAnimationHandler();
layoutManager = adapter.getLayoutManager();
}
void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
appsList.updateItemFilter(matcher);
recyclerView = (AllAppsRecyclerView) rv;
- recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
recyclerView.setApps(appsList, mUsingTabs);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
// No animations will occur when changes occur to the items in this RecyclerView.
recyclerView.setItemAnimator(null);
+ if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
+ recyclerView.setSpringAnimationHandler(animationHandler);
+ }
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
recyclerView.addItemDecoration(focusedItemDecorator);
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 27fc53a..a61521c 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.support.animation.DynamicAnimation;
+import android.support.animation.SpringAnimation;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -36,10 +38,11 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
@@ -68,6 +71,7 @@
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
+ public static final int VIEW_TYPE_MASK_HAS_SPRINGS = VIEW_TYPE_MASK_ICON;
public interface BindViewCallback {
@@ -178,6 +182,8 @@
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
+ private final View.OnClickListener mIconClickListener;
+ private final View.OnLongClickListener mIconLongClickListener;
private final int mAppsPerRow;
@@ -189,7 +195,10 @@
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
+ private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
+
+ public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
+ iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -198,11 +207,23 @@
mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mLayoutInflater = LayoutInflater.from(launcher);
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) {
+ mSpringAnimationHandler = new SpringAnimationHandler<>(
+ SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
+ } else {
+ mSpringAnimationHandler = null;
+ }
mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
mGridLayoutMgr.setSpanCount(mAppsPerRow);
}
+ public SpringAnimationHandler getSpringAnimationHandler() {
+ return mSpringAnimationHandler;
+ }
+
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
}
@@ -249,8 +270,8 @@
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
- icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
icon.setOnFocusChangeListener(mIconFocusListener);
@@ -323,6 +344,22 @@
}
@Override
+ public void onViewAttachedToWindow(ViewHolder holder) {
+ int type = holder.getItemViewType();
+ if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+ mSpringAnimationHandler.add(holder.itemView, holder);
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(ViewHolder holder) {
+ int type = holder.getItemViewType();
+ if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+ mSpringAnimationHandler.remove(holder.itemView);
+ }
+ }
+
+ @Override
public boolean onFailedToRecycleView(ViewHolder holder) {
// Always recycle and we will reset the view when it is bound
return true;
@@ -339,4 +376,104 @@
return item.viewType;
}
+ /**
+ * Helper class to set the SpringAnimation values for an item in the adapter.
+ */
+ private class AllAppsSpringAnimationFactory
+ implements SpringAnimationHandler.AnimationFactory<ViewHolder> {
+ private static final float DEFAULT_MAX_VALUE_PX = 100;
+ private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX;
+
+ // Damping ratio range is [0, 1]
+ private static final float SPRING_DAMPING_RATIO = 0.55f;
+
+ // Stiffness is a non-negative number.
+ private static final float MIN_SPRING_STIFFNESS = 580f;
+ private static final float MAX_SPRING_STIFFNESS = 900f;
+
+ // The amount by which each adjacent rows' stiffness will differ.
+ private static final float ROW_STIFFNESS_COEFFICIENT = 50f;
+
+ // The percentage by which we multiply each row to create the row factor.
+ private static final float ROW_PERCENTAGE = 0.3f;
+
+ @Override
+ public SpringAnimation initialize(ViewHolder vh) {
+ return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0);
+ }
+
+ /**
+ * @param spring A new or recycled SpringAnimation.
+ * @param vh The ViewHolder that {@param spring} is related to.
+ */
+ @Override
+ public void update(SpringAnimation spring, ViewHolder vh) {
+ int appPosition = vh.getAdapterPosition();
+ int col = appPosition % mAppsPerRow;
+ int row = appPosition / mAppsPerRow;
+
+ int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count
+ if (row > (numTotalRows / 2)) {
+ // Mirror the rows so that the top row acts the same as the bottom row.
+ row = Math.abs(numTotalRows - row);
+ }
+
+ calculateSpringValues(spring, row, col);
+ }
+
+ @Override
+ public void setDefaultValues(SpringAnimation spring) {
+ calculateSpringValues(spring, 0, mAppsPerRow / 2);
+ }
+
+ /**
+ * We manipulate the stiffness, min, and max values based on the items distance to the
+ * first row and the items distance to the center column to create the ^-shaped motion
+ * effect.
+ */
+ private void calculateSpringValues(SpringAnimation spring, int row, int col) {
+ float rowFactor = (1 + row) * ROW_PERCENTAGE;
+ float colFactor = getColumnFactor(col, mAppsPerRow);
+
+ float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor);
+ float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor);
+
+ float stiffness = Utilities.boundToRange(
+ MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT),
+ MIN_SPRING_STIFFNESS,
+ MAX_SPRING_STIFFNESS);
+
+ spring.setMinValue(minValue)
+ .setMaxValue(maxValue)
+ .getSpring()
+ .setStiffness(stiffness)
+ .setDampingRatio(SPRING_DAMPING_RATIO);
+ }
+
+ /**
+ * Increase the column factor as the distance increases between the column and the center
+ * column(s).
+ */
+ private float getColumnFactor(int col, int numCols) {
+ float centerColumn = numCols / 2;
+ int distanceToCenter = (int) Math.abs(col - centerColumn);
+
+ boolean evenNumberOfColumns = numCols % 2 == 0;
+ if (evenNumberOfColumns && col < centerColumn) {
+ distanceToCenter -= 1;
+ }
+
+ float factor = 0;
+ while (distanceToCenter > 0) {
+ if (distanceToCenter == 1) {
+ factor += 0.2f;
+ } else {
+ factor += 0.1f;
+ }
+ --distanceToCenter;
+ }
+
+ return factor;
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index b2e35a4..3b4450b 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -17,9 +17,10 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.MotionEvent;
+import android.view.MotionEvent;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
@@ -41,8 +42,8 @@
@Override
protected String getCurrentPageDescription() {
- // Not necessary, tab-bar already has two tabs with their own descriptions.
- return "";
+ return getResources().getString(
+ getNextPage() == 0 ? R.string.all_apps_personal_tab : R.string.all_apps_work_tab);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a7447b7..53d19eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -17,12 +17,14 @@
import static android.view.View.MeasureSpec.UNSPECIFIED;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
+import android.util.Property;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
@@ -33,8 +35,12 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -58,6 +64,23 @@
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
+ private SpringAnimationHandler mSpringAnimationHandler;
+ private OverScrollHelper mOverScrollHelper;
+ private SwipeDetector mPullDetector;
+ private float mContentTranslationY = 0;
+ public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
+ new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
+ @Override
+ public Float get(AllAppsRecyclerView allAppsRecyclerView) {
+ return allAppsRecyclerView.getContentTranslationY();
+ }
+
+ @Override
+ public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
+ allAppsRecyclerView.setContentTranslationY(y);
+ }
+ };
+
public AllAppsRecyclerView(Context context) {
this(context, null);
}
@@ -74,11 +97,33 @@
int defStyleRes) {
super(context, attrs, defStyleAttr);
Resources res = getResources();
+ addOnItemTouchListener(this);
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
R.dimen.all_apps_empty_search_bg_top_offset);
+
+ mOverScrollHelper = new OverScrollHelper();
+ mPullDetector = new SwipeDetector(getContext(), mOverScrollHelper, SwipeDetector.VERTICAL);
+ mPullDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
+
mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
}
+ public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
+ if (FeatureFlags.LAUNCHER3_PHYSICS) {
+ mSpringAnimationHandler = springAnimationHandler;
+ addOnScrollListener(new SpringMotionOnScrollListener());
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ mPullDetector.onTouchEvent(e);
+ if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
+ mSpringAnimationHandler.addMovement(e);
+ }
+ return super.onTouchEvent(e);
+ }
+
/**
* Sets the list of apps in this view, used to determine the fastscroll position.
*/
@@ -126,6 +171,26 @@
}
@Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.translate(0, mContentTranslationY);
+ super.dispatchDraw(canvas);
+ canvas.translate(0, -mContentTranslationY);
+ }
+
+ public float getContentTranslationY() {
+ return mContentTranslationY;
+ }
+
+ /**
+ * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
+ * on top of other Views.
+ */
+ public void setContentTranslationY(float y) {
+ mContentTranslationY = y;
+ invalidate();
+ }
+
+ @Override
protected boolean verifyDrawable(Drawable who) {
return who == mEmptySearchBackground || super.verifyDrawable(who);
}
@@ -167,7 +232,8 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
- boolean result = super.onInterceptTouchEvent(e);
+ mPullDetector.onTouchEvent(e);
+ boolean result = super.onInterceptTouchEvent(e) || mOverScrollHelper.isInOverScroll();
if (!result && e.getAction() == MotionEvent.ACTION_DOWN
&& mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
mEmptySearchBackground.setHotspot(e.getX(), e.getY());
@@ -415,4 +481,114 @@
y + mEmptySearchBackground.getIntrinsicHeight());
}
+ private class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener {
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (mOverScrollHelper.isInOverScroll()) {
+ // OverScroll will handle animating the springs.
+ return;
+ }
+
+ // We only start the spring animation when we hit the top/bottom, to ensure
+ // that all of the animations start at the same time.
+ if (dy < 0 && !canScrollVertically(-1)) {
+ mSpringAnimationHandler.animateToFinalPosition(0, 1);
+ } else if (dy > 0 && !canScrollVertically(1)) {
+ mSpringAnimationHandler.animateToFinalPosition(0, -1);
+ }
+ }
+ }
+
+ private class OverScrollHelper implements SwipeDetector.Listener {
+
+ private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
+ private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;
+
+ private boolean mIsInOverScroll;
+
+ // We use this value to calculate the actual amount the user has overscrolled.
+ private float mFirstDisplacement = 0;
+
+ private boolean mAlreadyScrollingUp;
+ private int mFirstScrollYOnScrollUp;
+
+ @Override
+ public void onDragStart(boolean start) {
+ }
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ boolean isScrollingUp = displacement > 0;
+ if (isScrollingUp) {
+ if (!mAlreadyScrollingUp) {
+ mFirstScrollYOnScrollUp = getCurrentScrollY();
+ mAlreadyScrollingUp = true;
+ }
+ } else {
+ mAlreadyScrollingUp = false;
+ }
+
+ // Only enter overscroll if the user is interacting with the RecyclerView directly
+ // and if one of the following criteria are met:
+ // - User scrolls down when they're already at the bottom.
+ // - User starts scrolling up, hits the top, and continues scrolling up.
+ boolean wasInOverScroll = mIsInOverScroll;
+ mIsInOverScroll = !mScrollbar.isDraggingThumb() &&
+ ((!canScrollVertically(1) && displacement < 0) ||
+ (!canScrollVertically(-1) && isScrollingUp && mFirstScrollYOnScrollUp != 0));
+
+ if (wasInOverScroll && !mIsInOverScroll) {
+ // Exit overscroll. This can happen when the user is in overscroll and then
+ // scrolls the opposite way.
+ reset(false /* shouldSpring */);
+ } else if (mIsInOverScroll) {
+ if (Float.compare(mFirstDisplacement, 0) == 0) {
+ // Because users can scroll before entering overscroll, we need to
+ // subtract the amount where the user was not in overscroll.
+ mFirstDisplacement = displacement;
+ }
+ float overscrollY = displacement - mFirstDisplacement;
+ setContentTranslationY(getDampedOverScroll(overscrollY));
+ }
+
+ return mIsInOverScroll;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ reset(mIsInOverScroll /* shouldSpring */);
+ }
+
+ private void reset(boolean shouldSpring) {
+ float y = getContentTranslationY();
+ if (Float.compare(y, 0) != 0) {
+ if (mSpringAnimationHandler != null && shouldSpring) {
+ // We calculate our own velocity to give the springs the desired effect.
+ float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
+ // We want to negate the velocity because we are moving to 0 from -1 due to the
+ // downward motion. (y-axis -1 is above 0).
+ mSpringAnimationHandler.animateToPositionWithVelocity(0, -1, -velocity);
+ }
+
+ ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
+ AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
+ .setDuration(100)
+ .start();
+ }
+ mIsInOverScroll = false;
+ mFirstDisplacement = 0;
+ mFirstScrollYOnScrollUp = 0;
+ mAlreadyScrollingUp = false;
+ }
+
+ public boolean isInOverScroll() {
+ return mIsInOverScroll;
+ }
+
+ private float getDampedOverScroll(float y) {
+ return OverScroll.dampedScroll(y, getHeight());
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 13a42f1..c859347 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -16,14 +16,17 @@
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.SearchUiManager.OnScrollRangeChangeListener;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.AllAppsScrim;
/**
* Handles AllApps view transition.
@@ -55,6 +58,7 @@
public static final float PARALLAX_COEFFICIENT = .125f;
private AllAppsContainerView mAppsView;
+ private Workspace mWorkspace;
private Hotseat mHotseat;
private final Launcher mLauncher;
@@ -72,6 +76,8 @@
private static final float DEFAULT_SHIFT_RANGE = 10;
+ private AllAppsScrim mAllAppsScrim;
+
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
mShiftRange = DEFAULT_SHIFT_RANGE;
@@ -100,6 +106,7 @@
mAppsView.setAlpha(1);
mLauncher.getHotseat().setTranslationY(0);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
}
}
@@ -120,21 +127,25 @@
float alpha = 1 - workspaceHotseatAlpha;
mAppsView.setTranslationY(shiftCurrent);
+ if (mAllAppsScrim == null) {
+ mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
+ }
float hotseatTranslation = -mShiftRange + shiftCurrent;
+ mAllAppsScrim.setProgress(hotseatTranslation, alpha);
if (!mIsVerticalLayout) {
mAppsView.setAlpha(alpha);
mLauncher.getHotseat().setTranslationY(hotseatTranslation);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
- }
- // Use a light system UI (dark icons) if all apps is behind at least half of the
- // status bar.
- boolean forceChange = shiftCurrent <= mShiftRange / 4;
- if (forceChange) {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
- } else {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
+ // Use a light system UI (dark icons) if all apps is behind at least half of the
+ // status bar.
+ boolean forceChange = shiftCurrent <= mShiftRange / 4;
+ if (forceChange) {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
+ } else {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
+ }
}
}
@@ -190,9 +201,10 @@
};
}
- public void setupViews(AllAppsContainerView appsView, Hotseat hotseat) {
+ public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
mAppsView = appsView;
mHotseat = hotseat;
+ mWorkspace = workspace;
mHotseat.bringToFront();
mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index a0dc5a3..d8a9f63 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import com.android.launcher3.R;
@@ -67,7 +68,6 @@
private int mTranslationY;
private boolean mForwardToRecyclerView;
- protected boolean mTabsHidden;
protected int mMaxTranslation;
public FloatingHeaderView(@NonNull Context context) {
@@ -85,7 +85,6 @@
}
public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
- mTabsHidden = tabsHidden;
mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
@@ -106,13 +105,7 @@
}
public int getMaxTranslation() {
- if (mMaxTranslation == 0 && mTabsHidden) {
- return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
- } else if (mMaxTranslation > 0 && mTabsHidden) {
- return mMaxTranslation + getPaddingTop();
- } else {
- return mMaxTranslation;
- }
+ return mMaxTranslation;
}
private boolean canSnapAt(int currentScrollY) {
@@ -138,7 +131,7 @@
mSnappedScrolledY = currentScrollY - mMaxTranslation;
} else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
mHeaderCollapsed = true;
- mSnappedScrolledY = -mMaxTranslation;
+ mSnappedScrolledY = currentScrollY;
}
}
}
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index d8568f8..bb17ed5 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -37,7 +37,7 @@
/**
* Notifies the search manager to close any active search session.
*/
- void resetSearch();
+ void reset();
/**
* Called before dispatching a key event, in case the search manager wants to initialize
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index dd80dac..a56c8b8 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -15,12 +15,6 @@
*/
package com.android.launcher3.allapps.search;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.getSize;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import static com.android.launcher3.graphics.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-
import android.content.Context;
import android.graphics.Rect;
import android.support.animation.FloatValueHolder;
@@ -35,7 +29,7 @@
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.FrameLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
@@ -53,16 +47,20 @@
/**
* Layout to contain the All-apps search UI.
*/
-public class AppsSearchContainerLayout extends ExtendedEditText
+public class AppsSearchContainerLayout extends FrameLayout
implements SearchUiManager, AllAppsSearchBarController.Callbacks,
AllAppsStore.OnUpdateListener {
-
private final Launcher mLauncher;
+ private final int mMinHeight;
+ private final int mSearchBoxHeight;
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
+ private ExtendedEditText mSearchInput;
private AlphabeticalAppsList mApps;
+ private View mDivider;
+ private HeaderElevationController mElevationController;
private AllAppsContainerView mAppsView;
private SpringAnimation mSpring;
@@ -78,22 +76,39 @@
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
+ mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
+ mSearchBoxHeight = getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height);
mSearchBarController = new AllAppsSearchBarController();
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
+ // Note: This spring does nothing.
+ mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSearchInput = findViewById(R.id.search_box_input);
+ mDivider = findViewById(R.id.search_divider);
+ mElevationController = new HeaderElevationController(mDivider);
+
// Update the hint to contain the icon.
// Prefix the original hint with two spaces. The first space gets replaced by the icon
// using span. The second space is used for a singe space character between the hint
// and the icon.
- SpannableString spanned = new SpannableString(" " + getHint());
+ SpannableString spanned = new SpannableString(" " + mSearchInput.getHint());
spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
- setHint(spanned);
+ mSearchInput.setHint(spanned);
- // Note: This spring does nothing.
- mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ if (!dp.isVerticalBarLayout()) {
+ LayoutParams lp = (LayoutParams) mDivider.getLayoutParams();
+ lp.leftMargin = lp.rightMargin = dp.edgeMarginPx;
+ }
}
@Override
@@ -110,39 +125,21 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Update the width to match the grid padding
DeviceProfile dp = mLauncher.getDeviceProfile();
- int myRequestedWidth = getSize(widthMeasureSpec);
- int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
- - mAppsView.getActiveRecyclerView().getPaddingRight();
-
- int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons);
- int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
- int iconPadding = cellWidth - iconVisibleSize;
-
- int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight();
- super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec);
+ if (!dp.isVerticalBarLayout()) {
+ getLayoutParams().height = dp.getInsets().top + mMinHeight;
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- // Shift the widget horizontally so that its centered in the parent (b/63428078)
- View parent = (View) getParent();
- int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
- int myWidth = right - left;
- int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
- int shift = expectedLeft - left;
- setTranslationX(shift);
- }
@Override
public void initialize(AllAppsContainerView appsView) {
mApps = appsView.getApps();
mAppsView = appsView;
+ appsView.addElevationController(mElevationController);
mSearchBarController.initialize(
- new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
+ new DefaultAppSearchAlgorithm(mApps.getApps()), mSearchInput, mLauncher, this);
}
@Override
@@ -156,7 +153,8 @@
}
@Override
- public void resetSearch() {
+ public void reset() {
+ mElevationController.reset();
mSearchBarController.reset();
}
@@ -202,6 +200,7 @@
}
private void notifyResultChanged() {
+ mElevationController.reset();
mAppsView.onSearchResultsChanged();
}
@@ -215,9 +214,9 @@
if (!dp.isVerticalBarLayout()) {
Rect insets = dp.getInsets();
int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
- MarginLayoutParams mlp = ((MarginLayoutParams) getLayoutParams());
- int myBot = mlp.topMargin + (int) getTranslationY() + mlp.height;
- listener.onScrollRangeChanged(hotseatBottom - myBot);
+ int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
+ + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
+ listener.onScrollRangeChanged(hotseatBottom - searchTopMargin);
} else {
listener.onScrollRangeChanged(bottom);
}
diff --git a/src/com/android/launcher3/allapps/search/HeaderElevationController.java b/src/com/android/launcher3/allapps/search/HeaderElevationController.java
new file mode 100644
index 0000000..7cd32b2
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/HeaderElevationController.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.allapps.search;
+
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+
+import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.R;
+
+/**
+ * Helper class for controlling the header elevation in response to RecyclerView scroll.
+ */
+public class HeaderElevationController extends RecyclerView.OnScrollListener {
+
+ private final View mHeader;
+ private final View mHeaderChild;
+ private final float mMaxElevation;
+ private final float mScrollToElevation;
+
+ private int mCurrentY = 0;
+
+ public HeaderElevationController(View header) {
+ mHeader = header;
+ final Resources res = mHeader.getContext().getResources();
+ mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
+ mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
+
+ // We need to provide a custom outline so the shadow only appears on the bottom edge.
+ // The top, left and right edges are all extended out to match parent's edge, so that
+ // the shadow is clipped by the parent.
+ final ViewOutlineProvider vop = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ // Set the left and top to be at the parents edge. Since the coordinates are
+ // relative to this view,
+ // (x = -view.getLeft()) for this view => (x = 0) for parent
+ final int left = -view.getLeft();
+ final int top = -view.getTop();
+
+ // Since the view is centered align, the spacing on left and right are same.
+ // Add same spacing on the right to reach parent's edge.
+ final int right = view.getWidth() - left;
+ final int bottom = view.getHeight();
+ final int offset = (int) mMaxElevation;
+ outline.setRect(left - offset, top - offset, right + offset, bottom);
+ }
+ };
+ mHeader.setOutlineProvider(vop);
+ mHeaderChild = ((ViewGroup) mHeader).getChildAt(0);
+ }
+
+ public void reset() {
+ mCurrentY = 0;
+ onScroll(mCurrentY);
+ }
+
+ @Override
+ public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mCurrentY = ((BaseRecyclerView) recyclerView).getCurrentScrollY();
+ onScroll(mCurrentY);
+ }
+
+ private void onScroll(int scrollY) {
+ float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
+ float newElevation = mMaxElevation * elevationPct;
+ if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
+ mHeader.setElevation(newElevation);
+
+ // To simulate a scrolling effect for the header, we translate the header down, and
+ // its content up by the same amount, so that it gets clipped by the parent, making it
+ // look like the content was scrolled out of the view.
+ int shift = Math.min(mHeader.getHeight(), scrollY);
+ mHeader.setTranslationY(-shift);
+ mHeaderChild.setTranslationY(shift);
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
new file mode 100644
index 0000000..29a2430
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -0,0 +1,265 @@
+/*
+ * 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.anim;
+
+import android.support.animation.FloatPropertyCompat;
+import android.support.animation.SpringAnimation;
+import android.support.animation.SpringForce;
+import android.support.annotation.IntDef;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Handler class that manages springs for a set of views that should all move based on the same
+ * {@link MotionEvent}s.
+ *
+ * Supports setting either X or Y velocity on the list of springs added to this handler.
+ */
+public class SpringAnimationHandler<T> {
+
+ private static final String TAG = "SpringAnimationHandler";
+ private static final boolean DEBUG = false;
+
+ private static final float VELOCITY_DAMPING_FACTOR = 0.175f;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Y_DIRECTION, X_DIRECTION})
+ public @interface Direction {}
+ public static final int Y_DIRECTION = 0;
+ public static final int X_DIRECTION = 1;
+ private int mVelocityDirection;
+
+ private VelocityTracker mVelocityTracker;
+ private float mCurrentVelocity = 0;
+ private boolean mShouldComputeVelocity = false;
+
+ private AnimationFactory<T> mAnimationFactory;
+
+ private ArrayList<SpringAnimation> mAnimations = new ArrayList<>();
+
+ /**
+ * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}.
+ * Determines which direction we use to calculate and set the velocity.
+ * @param factory The AnimationFactory is responsible for initializing and updating the
+ * SpringAnimations added to this class.
+ */
+ public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) {
+ mVelocityDirection = direction;
+ mAnimationFactory = factory;
+ }
+
+ /**
+ * Adds a spring to the list of springs handled by this class.
+ * @param spring The new spring to be added.
+ * @param setDefaultValues If True, sets the spring to the default
+ * {@link AnimationFactory} values.
+ */
+ public void add(SpringAnimation spring, boolean setDefaultValues) {
+ if (setDefaultValues) {
+ mAnimationFactory.setDefaultValues(spring);
+ }
+ spring.setStartVelocity(mCurrentVelocity);
+ mAnimations.add(spring);
+ }
+
+ public AnimationFactory<T> getFactory() {
+ return mAnimationFactory;
+ }
+
+ /**
+ * Adds a new or recycled animation to the list of springs handled by this class.
+ *
+ * @param view The view the spring is attached to.
+ * @param object Used to initialize and update the spring.
+ */
+ public void add(View view, T object) {
+ SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
+ if (spring == null) {
+ spring = mAnimationFactory.initialize(object);
+ view.setTag(R.id.spring_animation_tag, spring);
+ }
+ mAnimationFactory.update(spring, object);
+ add(spring, false /* setDefaultValues */);
+ }
+
+ /**
+ * Stops and removes the spring attached to {@param view}.
+ */
+ public void remove(View view) {
+ remove((SpringAnimation) view.getTag(R.id.spring_animation_tag));
+ }
+
+ public void remove(SpringAnimation animation) {
+ if (animation.canSkipToEnd()) {
+ animation.skipToEnd();
+ }
+ mAnimations.remove(animation);
+ }
+
+ public void addMovement(MotionEvent event) {
+ int action = event.getActionMasked();
+ if (DEBUG) Log.d(TAG, "addMovement#action=" + action);
+ switch (action) {
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_DOWN:
+ reset();
+ break;
+ }
+
+ getVelocityTracker().addMovement(event);
+ mShouldComputeVelocity = true;
+ }
+
+ public void animateToFinalPosition(float position, int startValue) {
+ animateToFinalPosition(position, startValue, mShouldComputeVelocity);
+ }
+
+ /**
+ * @param position The final animation position.
+ * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start
+ * The magnitude of the number changes how the spring will move.
+ * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before
+ * starting the animation.
+ */
+ private void animateToFinalPosition(float position, int startValue, boolean setVelocity) {
+ if (DEBUG) {
+ Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue);
+ }
+
+ if (mShouldComputeVelocity) {
+ mCurrentVelocity = computeVelocity();
+ }
+
+ int size = mAnimations.size();
+ for (int i = 0; i < size; ++i) {
+ mAnimations.get(i).setStartValue(startValue);
+ if (setVelocity) {
+ mAnimations.get(i).setStartVelocity(mCurrentVelocity);
+ }
+ mAnimations.get(i).animateToFinalPosition(position);
+ }
+
+ reset();
+ }
+
+ /**
+ * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to
+ * manually set the velocity.
+ */
+ public void animateToPositionWithVelocity(float position, int startValue, float velocity) {
+ if (DEBUG) {
+ Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue
+ + ", velocity=" + velocity);
+ }
+
+ mCurrentVelocity = velocity;
+ mShouldComputeVelocity = false;
+ animateToFinalPosition(position, startValue, true);
+ }
+
+
+ public boolean isRunning() {
+ // All the animations run at the same time so we can just check the first one.
+ return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
+ }
+
+ public void skipToEnd() {
+ if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd");
+ if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception());
+
+ int size = mAnimations.size();
+ for (int i = 0; i < size; ++i) {
+ if (mAnimations.get(i).canSkipToEnd()) {
+ mAnimations.get(i).skipToEnd();
+ }
+ }
+ }
+
+ public void reset() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ mCurrentVelocity = 0;
+ mShouldComputeVelocity = false;
+ }
+
+
+ private float computeVelocity() {
+ getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
+
+ float velocity = isVerticalDirection()
+ ? getVelocityTracker().getYVelocity()
+ : getVelocityTracker().getXVelocity();
+ velocity *= VELOCITY_DAMPING_FACTOR;
+
+ if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity);
+ return velocity;
+ }
+
+ private boolean isVerticalDirection() {
+ return mVelocityDirection == Y_DIRECTION;
+ }
+
+ private VelocityTracker getVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ return mVelocityTracker;
+ }
+
+ /**
+ * This interface is used to initialize and update the SpringAnimations added to the
+ * {@link SpringAnimationHandler}.
+ *
+ * @param <T> The object that each SpringAnimation is attached to.
+ */
+ public interface AnimationFactory<T> {
+
+ /**
+ * Initializes a new Spring for {@param object}.
+ */
+ SpringAnimation initialize(T object);
+
+ /**
+ * Updates the value of {@param spring} based on {@param object}.
+ */
+ void update(SpringAnimation spring, T object);
+
+ /**
+ * Sets the factory default values for the given {@param spring}.
+ */
+ void setDefaultValues(SpringAnimation spring);
+ }
+
+ /**
+ * Helper method to create a new SpringAnimation for {@param view}.
+ */
+ public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
+ SpringAnimation spring = new SpringAnimation(view, property, finalPos);
+ spring.setSpring(new SpringForce(finalPos));
+ return spring;
+ }
+
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 9487427..72d49f0 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -80,7 +80,7 @@
Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
return;
}
- canvas.save();
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
// We draw the badge relative to its center.
float badgeCenterX = iconBounds.right - mDotCenterOffset / 2;
float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 03e3861..62055dc 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -58,6 +58,7 @@
public abstract long getSerialNumberForUser(UserHandle user);
public abstract UserHandle getUserForSerialNumber(long serialNumber);
public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user);
+ public abstract long getUserCreationTime(UserHandle user);
public abstract boolean isQuietModeEnabled(UserHandle user);
public abstract boolean isUserUnlocked(UserHandle user);
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 1ff6981..e57786d 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -23,6 +23,7 @@
import android.os.UserManager;
import android.util.ArrayMap;
import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.ManagedProfileHeuristic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -126,5 +127,15 @@
}
return mPm.getUserBadgedLabel(label, user);
}
+
+ @Override
+ public long getUserCreationTime(UserHandle user) {
+ SharedPreferences prefs = ManagedProfileHeuristic.prefs(mContext);
+ String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
+ if (!prefs.contains(key)) {
+ prefs.edit().putLong(key, System.currentTimeMillis()).apply();
+ }
+ return prefs.getLong(key, 0);
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVM.java b/src/com/android/launcher3/compat/UserManagerCompatVM.java
index cf74b37..75c1877 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVM.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVM.java
@@ -27,4 +27,9 @@
UserManagerCompatVM(Context context) {
super(context);
}
+
+ @Override
+ public long getUserCreationTime(UserHandle user) {
+ return mUserManager.getUserCreationTime(user);
+ }
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 28645dc..e494bea 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -35,6 +35,8 @@
public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
+ // When enabled allows use of physics based motions in the Launcher.
+ public static final boolean LAUNCHER3_PHYSICS = true;
// When enabled allows use of spring motions on the icons.
public static final boolean LAUNCHER3_SPRING_ICONS = true;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 95e1034..db199c1 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -16,11 +16,6 @@
package com.android.launcher3.dragndrop;
-import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.appwidget.AppWidgetManager;
@@ -28,6 +23,7 @@
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.LauncherApps.PinItemRequest;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PointF;
@@ -47,6 +43,7 @@
import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompatVO;
import com.android.launcher3.model.WidgetItem;
@@ -58,6 +55,11 @@
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetImageView;
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+
@TargetApi(Build.VERSION_CODES.O)
public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
@@ -152,7 +154,15 @@
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- listener.initWhenReady();
+ if (!getResources().getBoolean(R.bool.allow_rotation) &&
+ !Utilities.isAllowRotationPrefEnabled(this) &&
+ (getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode())) {
+ // If we are starting the drag in landscape even though home is locked in portrait,
+ // restart the home activity to temporarily allow rotation.
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+
startActivity(homeIntent,
ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
mFinishOnPause = true;
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index df4a7c1..9638a75 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -17,8 +17,6 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.content.ClipDescription;
import android.content.Intent;
@@ -81,7 +79,6 @@
AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
launcher.getDragLayer().setOnDragListener(this);
- launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
mLauncher = launcher;
mDragController = launcher.getDragController();
@@ -160,7 +157,6 @@
}
private void postCleanup() {
- clearReference();
if (mLauncher != null) {
// Remove any drag params from the launcher intent since the drag operation is complete.
Intent newIntent = new Intent(mLauncher.getIntent());
@@ -168,12 +164,16 @@
mLauncher.setIntent(newIntent);
}
- new Handler(Looper.getMainLooper()).post(this::removeListener);
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ removeListener();
+ }
+ });
}
public void removeListener() {
if (mLauncher != null) {
- mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
mLauncher.getDragLayer().setOnDragListener(null);
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f5d0b24..1cf407e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -27,6 +27,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -49,7 +50,6 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.ViewScrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Thunk;
@@ -88,6 +88,7 @@
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
+ private final PageCutOutScrimDrawable mPageCutOutScrim;
protected TouchController[] mControllers;
private TouchController mActiveController;
@@ -105,6 +106,8 @@
setChildrenDrawingOrderEnabled(true);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
+ mPageCutOutScrim = new PageCutOutScrimDrawable(this);
+ mPageCutOutScrim.setCallback(this);
}
public void setup(Launcher launcher, DragController dragController) {
@@ -122,6 +125,11 @@
return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
}
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mPageCutOutScrim;
+ }
+
public boolean isEventOverHotseat(MotionEvent ev) {
return isEventOverView(mLauncher.getHotseat(), ev);
}
@@ -140,15 +148,6 @@
}
@Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- ViewScrim scrim = ViewScrim.get(child);
- if (scrim != null) {
- scrim.draw(canvas, getWidth(), getHeight());
- }
- return super.drawChild(canvas, child, drawingTime);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
@@ -172,11 +171,6 @@
return true;
}
- if (mLauncher.getStateManager().getState().disableInteraction) {
- // You Shall Not Pass!!!
- return true;
- }
-
if (mDragController.onControllerInterceptTouchEvent(ev)) {
mActiveController = mDragController;
return true;
@@ -542,7 +536,11 @@
final int fromX = r.left;
final int fromY = r.top;
child.setVisibility(INVISIBLE);
- Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
+ Runnable onCompleteRunnable = new Runnable() {
+ public void run() {
+ child.setVisibility(VISIBLE);
+ }
+ };
animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
}
@@ -709,14 +707,12 @@
public void onViewAdded(View child) {
super.onViewAdded(child);
updateChildIndices();
- UiFactory.onLauncherStateOrFocusChanged(mLauncher);
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
updateChildIndices();
- UiFactory.onLauncherStateOrFocusChanged(mLauncher);
}
@Override
@@ -762,9 +758,20 @@
}
}
+ public void invalidateScrim() {
+ if (mPageCutOutScrim.getAlpha() > 0) {
+ invalidate();
+ }
+ }
+
+ public Drawable getScrim() {
+ return mPageCutOutScrim;
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the background below children.
+ mPageCutOutScrim.draw(canvas);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8d4f2ef..a59b899 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -448,7 +448,7 @@
canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
if (crossFade) {
mPaint.setAlpha((int) (255 * mCrossFadeProgress));
- final int saveCount = canvas.save();
+ final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
canvas.scale(sX, sY);
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 5576d91..1c6f77c 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -36,7 +36,7 @@
import com.android.launcher3.R;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
-import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Preconditions;
/**
@@ -113,7 +113,7 @@
final float previewShiftX = shiftFactor * previewWidth;
final float previewShiftY = shiftFactor * previewHeight;
- Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
+ Bitmap previewBitmap = UiFactory.createFromRenderer(previewWidth, previewHeight, false,
(canvas) -> {
int count = canvas.save();
canvas.translate(previewShiftX, previewShiftY);
diff --git a/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
new file mode 100644
index 0000000..367124b
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
+
+/**
+ * Scrim drawable which draws a hole for the current drop target page.
+ */
+public class PageCutOutScrimDrawable extends Drawable {
+
+ private final Rect mHighlightRect = new Rect();
+ private final DragLayer mDragLayer;
+ private final Launcher mLauncher;
+ private final WallpaperColorInfo mWallpaperColorInfo;
+
+ private int mAlpha = 0;
+
+ public PageCutOutScrimDrawable(DragLayer dragLayer) {
+ mDragLayer = dragLayer;
+ mLauncher = Launcher.getLauncher(mDragLayer.getContext());
+ mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // Draw the background below children.
+ if (mAlpha > 0) {
+ // Update the scroll position first to ensure scrim cutout is in the right place.
+ mLauncher.getWorkspace().computeScrollWithoutInvalidation();
+ CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
+ canvas.save();
+ if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
+ // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+ mDragLayer.getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+ canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+ }
+ // for super light wallpaper it needs to be darken for contrast to workspace
+ // for dark wallpapers the text is white so darkening works as well
+ int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor());
+ canvas.drawColor(ColorUtils.setAlphaComponent(color, mAlpha));
+ canvas.restore();
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ if (mAlpha != alpha) {
+ mAlpha = alpha;
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) { }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}
diff --git a/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
index 267e930..80a89e3 100644
--- a/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
+++ b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
@@ -2,6 +2,7 @@
import android.content.Context;
import android.graphics.Color;
+import android.support.v4.graphics.ColorUtils;
import android.util.Pair;
import com.android.launcher3.compat.WallpaperColorsCompat;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 1bdd554..993663e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import android.animation.Animator;
@@ -85,7 +86,7 @@
/**
* Represents a set of icons chosen by the user or generated by the system.
*/
-public class Folder extends AbstractFloatingView implements DragSource,
+public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
@@ -207,11 +208,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mContent = findViewById(R.id.folder_content);
+ mContent = (FolderPagedView) findViewById(R.id.folder_content);
mContent.setFolder(this);
- mPageIndicator = findViewById(R.id.folder_page_indicator);
- mFolderName = findViewById(R.id.folder_name);
+ mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
+ mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
mFolderName.setOnBackKeyListener(this);
mFolderName.setOnFocusChangeListener(this);
@@ -252,6 +253,13 @@
mFooterHeight = mFooter.getMeasuredHeight();
}
+ public void onClick(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ mLauncher.onClick(v);
+ }
+ }
+
public boolean onLongClick(View v) {
// Return if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return true;
@@ -922,7 +930,7 @@
int centeredTop = centerY - height / 2;
// We need to bound the folder to the currently visible workspace area
- if (mLauncher.getStateManager().getState().overviewUi) {
+ if (mLauncher.isInState(OVERVIEW)) {
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(),
sTempRect);
} else {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6c94273..2de09b8 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,7 +63,6 @@
import com.android.launcher3.dragndrop.BaseItemDragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -160,14 +159,14 @@
.inflate(resId, group, false);
icon.setClipToPadding(false);
- icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
+ icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
icon.setTag(folderInfo);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
+ icon.setOnClickListener(launcher);
icon.mInfo = folderInfo;
icon.mLauncher = launcher;
icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
@@ -468,10 +467,11 @@
final int saveCount;
if (canvas.isHardwareAccelerated()) {
- saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
+ saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
} else {
- saveCount = canvas.save();
- canvas.clipPath(mBackground.getClipPath());
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT);
}
mPreviewItemManager.draw(canvas);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index fa7565a..a468cb5 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -45,7 +45,6 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -238,7 +237,7 @@
R.layout.folder_application, null, false);
textView.applyFromShortcutInfo(item);
textView.setHapticFeedbackEnabled(false);
- textView.setOnClickListener(ItemClickHandler.INSTANCE);
+ textView.setOnClickListener(mFolder);
textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
textView.setOnKeyListener(mKeyListener);
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index a0912a4..285aef8 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -227,10 +227,11 @@
final int saveCount;
if (canvas.isHardwareAccelerated()) {
saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
- offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius, null);
+ offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
+ null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
} else {
- saveCount = canvas.save();
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipPath(getClipPath(), Region.Op.DIFFERENCE);
}
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 1f69f6e..06d3eb1 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -168,7 +168,7 @@
}
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
- canvas.save();
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(params.transX, params.transY);
canvas.scale(params.scale, params.scale);
Drawable d = params.drawable;
diff --git a/src/com/android/launcher3/graphics/BitmapRenderer.java b/src/com/android/launcher3/graphics/BitmapRenderer.java
index 3d11c44..4652ded 100644
--- a/src/com/android/launcher3/graphics/BitmapRenderer.java
+++ b/src/com/android/launcher3/graphics/BitmapRenderer.java
@@ -15,40 +15,9 @@
*/
package com.android.launcher3.graphics;
-import android.annotation.TargetApi;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Picture;
-import android.os.Build;
-import com.android.launcher3.Utilities;
+public interface BitmapRenderer {
-public class BitmapRenderer {
-
- public static final boolean USE_HARDWARE_BITMAP = Utilities.ATLEAST_P;
-
- public static Bitmap createSoftwareBitmap(int width, int height, Renderer renderer) {
- Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- renderer.draw(new Canvas(result));
- return result;
- }
-
- @TargetApi(Build.VERSION_CODES.P)
- public static Bitmap createHardwareBitmap(int width, int height, Renderer renderer) {
- if (!USE_HARDWARE_BITMAP) {
- return createSoftwareBitmap(width, height, renderer);
- }
-
- Picture picture = new Picture();
- renderer.draw(picture.beginRecording(width, height));
- picture.endRecording();
- return Bitmap.createBitmap(picture);
- }
-
- /**
- * Interface representing a bitmap draw operation.
- */
- public interface Renderer {
- void draw(Canvas out);
- }
+ void render(Canvas out);
}
diff --git a/src/com/android/launcher3/graphics/ColorScrim.java b/src/com/android/launcher3/graphics/ColorScrim.java
deleted file mode 100644
index 1ffce18..0000000
--- a/src/com/android/launcher3/graphics/ColorScrim.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.graphics;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dynamicui.WallpaperColorInfo;
-
-/**
- * Simple scrim which draws a color
- */
-public class ColorScrim extends ViewScrim {
-
- private final int mColor;
- private final Interpolator mInterpolator;
- private int mCurrentColor;
-
- public ColorScrim(View view, int color, Interpolator interpolator) {
- super(view);
- mColor = color;
- mInterpolator = interpolator;
- }
-
- @Override
- protected void onProgressChanged() {
- mCurrentColor = ColorUtils.setAlphaComponent(mColor,
- Math.round(mInterpolator.getInterpolation(mProgress) * Color.alpha(mColor)));
- }
-
- @Override
- public void draw(Canvas canvas, int width, int height) {
- if (mProgress > 0) {
- canvas.drawColor(mCurrentColor);
- }
- }
-
- public static ColorScrim createExtractedColorScrim(View view) {
- WallpaperColorInfo colors = WallpaperColorInfo.getInstance(view.getContext());
- int alpha = view.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
- ColorScrim scrim = new ColorScrim(view, ColorUtils.setAlphaComponent(
- colors.getSecondaryColor(), alpha), Interpolators.LINEAR);
- scrim.attach();
- return scrim;
- }
-}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index e60a2c7..6a328e9 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,17 +24,19 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -101,7 +103,7 @@
}
destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
-mView.getScrollY() + blurSizeOutline / 2);
- destCanvas.clipRect(clipRect);
+ destCanvas.clipRect(clipRect, Op.REPLACE);
mView.draw(destCanvas);
// Restore text visibility of FolderIcon if necessary
@@ -117,26 +119,28 @@
* Responsibility for the bitmap is transferred to the caller.
*/
public Bitmap createDragBitmap() {
+ float scale = 1f;
int width = mView.getWidth();
int height = mView.getHeight();
+ boolean forceSoftwareRenderer = false;
if (mView instanceof BubbleTextView) {
Drawable d = ((BubbleTextView) mView).getIcon();
Rect bounds = getDrawableBounds(d);
width = bounds.width();
height = bounds.height();
} else if (mView instanceof LauncherAppWidgetHostView) {
- float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
+ scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
width = (int) (mView.getWidth() * scale);
height = (int) (mView.getHeight() * scale);
// Use software renderer for widgets as we know that they already work
- return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ forceSoftwareRenderer = true;
}
- return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, 1));
+ final float scaleFinal = scale;
+ return UiFactory.createFromRenderer(width + blurSizeOutline, height + blurSizeOutline,
+ forceSoftwareRenderer, (c) -> drawDragView(c, scaleFinal));
}
public final void generateDragOutline(Bitmap preview) {
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 34a4e2d..32d9e41 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -17,7 +17,6 @@
package com.android.launcher3.graphics;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -66,12 +65,6 @@
* Returns a FastBitmapDrawable with the icon.
*/
public FastBitmapDrawable newIcon(ItemInfoWithIcon info) {
- FastBitmapDrawable drawable = new FastBitmapDrawable(info);
- drawable.setIsDisabled(info.isDisabled());
- return drawable;
- }
-
- public FastBitmapDrawable newIcon(BitmapInfo info, ActivityInfo target) {
return new FastBitmapDrawable(info);
}
@@ -85,6 +78,7 @@
return new PreloadIconDrawable(info, mPreloadProgressPath, context);
}
+
protected Path getPreloadProgressPath(Context context) {
if (Utilities.ATLEAST_OREO) {
try {
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
index 0f0e424..262a95e 100644
--- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java
+++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
@@ -29,7 +29,7 @@
@Override
public void draw(Canvas canvas) {
- int saveCount = canvas.save();
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScaleX, mScaleY,
getBounds().exactCenterX(), getBounds().exactCenterY());
super.draw(canvas);
diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java
new file mode 100644
index 0000000..6253e18
--- /dev/null
+++ b/src/com/android/launcher3/graphics/GradientView.java
@@ -0,0 +1,198 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.support.v4.graphics.ColorUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Draws a translucent radial gradient background from an initial state with progress 0.0 to a
+ * final state with progress 1.0;
+ */
+public class GradientView extends View implements WallpaperColorInfo.OnChangeListener {
+
+ private static final int DEFAULT_COLOR = Color.WHITE;
+ private static final int ALPHA_MASK_HEIGHT_DP = 500;
+ private static final int ALPHA_MASK_WIDTH_DP = 2;
+ private static final boolean DEBUG = false;
+
+ private final Bitmap mAlphaGradientMask;
+
+ private boolean mShowScrim = true;
+ private int mColor1 = DEFAULT_COLOR;
+ private int mColor2 = DEFAULT_COLOR;
+ private int mWidth;
+ private int mHeight;
+ private final RectF mAlphaMaskRect = new RectF();
+ private final RectF mFinalMaskRect = new RectF();
+ private final Paint mPaintWithScrim = new Paint();
+ private final Paint mPaintNoScrim = new Paint();
+ private float mProgress;
+ private final int mMaskHeight, mMaskWidth;
+ private final int mAlphaColors;
+ private final Paint mDebugPaint = DEBUG ? new Paint() : null;
+ private final Interpolator mAccelerator = Interpolators.ACCEL;
+ private final float mAlphaStart;
+ private final WallpaperColorInfo mWallpaperColorInfo;
+ private final int mScrimColor;
+
+ public GradientView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ this.mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
+ this.mMaskWidth = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
+ Launcher launcher = Launcher.getLauncher(context);
+ this.mAlphaStart = 0;
+ this.mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+ this.mWallpaperColorInfo = WallpaperColorInfo.getInstance(launcher);
+ mAlphaColors = getResources().getInteger(R.integer.extracted_color_gradient_alpha);
+ updateColors();
+ mAlphaGradientMask = createDitheredAlphaMask();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mWallpaperColorInfo.addOnChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mWallpaperColorInfo.removeOnChangeListener(this);
+ }
+
+ @Override
+ public void onExtractedColorsChanged(WallpaperColorInfo info) {
+ updateColors();
+ invalidate();
+ }
+
+ private void updateColors() {
+ this.mColor1 = ColorUtils.setAlphaComponent(mWallpaperColorInfo.getMainColor(),
+ mAlphaColors);
+ this.mColor2 = ColorUtils.setAlphaComponent(mWallpaperColorInfo.getSecondaryColor(),
+ mAlphaColors);
+ if (mWidth + mHeight > 0) {
+ createRadialShader();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ this.mWidth = getMeasuredWidth();
+ this.mHeight = getMeasuredHeight();
+ if (mWidth + mHeight > 0) {
+ createRadialShader();
+ }
+ }
+
+ // only being called when colors change
+ private void createRadialShader() {
+ final float gradientCenterY = 1.05f;
+ float radius = Math.max(mHeight, mWidth) * gradientCenterY;
+ float posScreenBottom = (radius - mHeight) / radius; // center lives below screen
+
+ RadialGradient shaderNoScrim = new RadialGradient(
+ mWidth * 0.5f,
+ mHeight * gradientCenterY,
+ radius,
+ new int[] {mColor1, mColor1, mColor2},
+ new float[] {0f, posScreenBottom, 1f},
+ Shader.TileMode.CLAMP);
+ mPaintNoScrim.setShader(shaderNoScrim);
+
+ int color1 = ColorUtils.compositeColors(mScrimColor,mColor1);
+ int color2 = ColorUtils.compositeColors(mScrimColor,mColor2);
+ RadialGradient shaderWithScrim = new RadialGradient(
+ mWidth * 0.5f,
+ mHeight * gradientCenterY,
+ radius,
+ new int[] { color1, color1, color2 },
+ new float[] {0f, posScreenBottom, 1f},
+ Shader.TileMode.CLAMP);
+ mPaintWithScrim.setShader(shaderWithScrim);
+ }
+
+ public void setProgress(float progress) {
+ setProgress(progress, true);
+ }
+
+ public void setProgress(float progress, boolean showScrim) {
+ this.mProgress = progress;
+ this.mShowScrim = showScrim;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ Paint paint = mShowScrim ? mPaintWithScrim : mPaintNoScrim;
+
+ float head = 0.29f;
+ float linearProgress = head + (mProgress * (1f - head));
+ float startMaskY = (1f - linearProgress) * mHeight - mMaskHeight * linearProgress;
+ float interpolatedAlpha = (255 - mAlphaStart) * mAccelerator.getInterpolation(mProgress);
+ paint.setAlpha((int) (mAlphaStart + interpolatedAlpha));
+ float div = (float) Math.floor(startMaskY + mMaskHeight);
+ mAlphaMaskRect.set(0, startMaskY, mWidth, div);
+ mFinalMaskRect.set(0, div, mWidth, mHeight);
+ canvas.drawBitmap(mAlphaGradientMask, null, mAlphaMaskRect, paint);
+ canvas.drawRect(mFinalMaskRect, paint);
+
+ if (DEBUG) {
+ mDebugPaint.setColor(0xFF00FF00);
+ canvas.drawLine(0, startMaskY, mWidth, startMaskY, mDebugPaint);
+ canvas.drawLine(0, startMaskY + mMaskHeight, mWidth, startMaskY + mMaskHeight, mDebugPaint);
+ }
+ }
+
+ public Bitmap createDitheredAlphaMask() {
+ Bitmap dst = Bitmap.createBitmap(mMaskWidth, mMaskHeight, Bitmap.Config.ALPHA_8);
+ Canvas c = new Canvas(dst);
+ Paint paint = new Paint(Paint.DITHER_FLAG);
+ LinearGradient lg = new LinearGradient(0, 0, 0, mMaskHeight,
+ new int[]{
+ 0x00FFFFFF,
+ ColorUtils.setAlphaComponent(Color.WHITE, (int) (0xFF * 0.95)),
+ 0xFFFFFFFF},
+ new float[]{0f, 0.8f, 1f},
+ Shader.TileMode.CLAMP);
+ paint.setShader(lg);
+ c.drawRect(0, 0, mMaskWidth, mMaskHeight, paint);
+ return dst;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
new file mode 100644
index 0000000..ebfe1e7
--- /dev/null
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008 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.graphics;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ADAPTIVE_ICON;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.SparseArray;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.UiFactory;
+
+/**
+ * Utility class to generate shadow and outline effect, which are used for click feedback
+ * and drag-n-drop respectively.
+ */
+public class HolographicOutlineHelper {
+
+ /**
+ * Bitmap used as shadow for Adaptive icons
+ */
+ public static final Bitmap ADAPTIVE_ICON_SHADOW_BITMAP =
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+
+ private static HolographicOutlineHelper sInstance;
+
+ private final Canvas mCanvas = new Canvas();
+ private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+ private final float mShadowBitmapShift;
+ private final BlurMaskFilter mShadowBlurMaskFilter;
+
+ // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps
+ private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);
+
+ private HolographicOutlineHelper(Context context) {
+ mShadowBitmapShift = context.getResources().getDimension(R.dimen.blur_size_click_shadow);
+ mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL);
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ }
+
+ public static HolographicOutlineHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new HolographicOutlineHelper(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ public Bitmap createMediumDropShadow(BubbleTextView view) {
+ if (view.getTag() instanceof ItemInfoWithIcon &&
+ ((((ItemInfoWithIcon) view.getTag()).runtimeStatusFlags & FLAG_ADAPTIVE_ICON)
+ != 0)) {
+ return ADAPTIVE_ICON_SHADOW_BITMAP;
+ }
+ Drawable drawable = view.getIcon();
+ if (drawable == null) {
+ return null;
+ }
+
+ float scaleX = view.getScaleX();
+ float scaleY = view.getScaleY();
+ Rect rect = drawable.getBounds();
+
+ int bitmapWidth = (int) (rect.width() * scaleX);
+ int bitmapHeight = (int) (rect.height() * scaleY);
+ if (bitmapHeight <= 0 || bitmapWidth <= 0) {
+ return null;
+ }
+
+ int key = (bitmapWidth << 16) | bitmapHeight;
+ Bitmap cache = mBitmapCache.get(key);
+ if (cache == null) {
+ cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(cache);
+ mBitmapCache.put(key, cache);
+ } else {
+ mCanvas.setBitmap(cache);
+ mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ }
+
+ int saveCount = mCanvas.save();
+ mCanvas.scale(scaleX, scaleY);
+ mCanvas.translate(-rect.left, -rect.top);
+ if (!UiFactory.USE_HARDWARE_BITMAP) {
+ // TODO: Outline generation requires alpha extraction, which is costly for
+ // hardware bitmaps. Instead use canvas layer operations once its available.
+ drawable.draw(mCanvas);
+ }
+ mCanvas.restoreToCount(saveCount);
+ mCanvas.setBitmap(null);
+
+ mBlurPaint.setMaskFilter(mShadowBlurMaskFilter);
+
+ int extraSize = (int) (2 * mShadowBitmapShift);
+
+ int resultWidth = bitmapWidth + extraSize;
+ int resultHeight = bitmapHeight + extraSize;
+ key = (resultWidth << 16) | resultHeight;
+ Bitmap result = mBitmapCache.get(key);
+ if (result == null) {
+ result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(result);
+ } else {
+ // Use put instead of delete, to avoid unnecessary shrinking of cache array
+ mBitmapCache.put(key, null);
+ mCanvas.setBitmap(result);
+ mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ }
+ mCanvas.drawBitmap(cache, mShadowBitmapShift, mShadowBitmapShift, mBlurPaint);
+ mCanvas.setBitmap(null);
+ return result;
+ }
+
+ public void recycleShadowBitmap(Bitmap bitmap) {
+ if (bitmap != null && bitmap != ADAPTIVE_ICON_SHADOW_BITMAP) {
+ mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 5d99ba0..bd20c87 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -61,9 +61,6 @@
private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
- // Ratio of the diameter of an normalized circular icon to the actual icon size.
- public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
-
private final int mMaxSize;
private final Bitmap mBitmap;
private final Bitmap mBitmapARGB;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 3b5585b..34fc921 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -29,13 +29,11 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.os.Build;
@@ -54,6 +52,7 @@
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Provider;
import com.android.launcher3.util.Themes;
@@ -62,8 +61,6 @@
*/
public class LauncherIcons implements AutoCloseable {
- private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
-
public static final Object sPoolSync = new Object();
private static LauncherIcons sPool;
@@ -88,9 +85,6 @@
*/
public void recycle() {
synchronized (sPoolSync) {
- // Clear any temporary state variables
- mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-
next = sPool;
sPool = this;
}
@@ -112,9 +106,6 @@
private IconNormalizer mNormalizer;
private ShadowGenerator mShadowGenerator;
- private Drawable mWrapperIcon;
- private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-
// sometimes we store linked lists of these things
private LauncherIcons next;
@@ -182,16 +173,6 @@
* The bitmap is also visually normalized with other icons.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
- }
-
- /**
- * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
- * view or workspace. The icon is badged for {@param user}.
- * The bitmap is also visually normalized with other icons.
- */
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
- boolean isInstantApp) {
float[] scale = new float[1];
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
Bitmap bitmap = createIconBitmap(icon, scale[0]);
@@ -210,9 +191,6 @@
} else {
result = createIconBitmap(badged, 1f);
}
- } else if (isInstantApp) {
- badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
- result = bitmap;
} else {
result = bitmap;
}
@@ -231,23 +209,13 @@
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
}
- /**
- * Sets the background color used for wrapped adaptive icon
- */
- public void setWrapperBackgroundColor(int color) {
- mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
- }
-
private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
RectF outIconBounds, float[] outScale) {
float scale = 1f;
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
- if (mWrapperIcon == null) {
- mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
- .mutate();
- }
- AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+ mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
dr.setBounds(0, 0, 1, 1);
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
@@ -256,8 +224,6 @@
fsd.setScale(scale);
icon = dr;
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
-
- ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
@@ -336,7 +302,7 @@
} else {
icon.setBounds(left, top, left+width, top+height);
}
- mCanvas.save();
+ mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(mCanvas);
mCanvas.restore();
@@ -383,7 +349,7 @@
final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
result.color = badge.iconColor;
- result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
+ result.icon = UiFactory.createFromRenderer(mIconBitmapSize, mIconBitmapSize, false, (c) -> {
getShadowGenerator().recreateIcon(unbadgedfinal, c);
badgeWithDrawable(c, new FastBitmapDrawable(badge));
});
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 42ba191..a40b6df 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -162,9 +162,9 @@
}
@Override
- public void drawInternal(Canvas canvas, Rect bounds) {
+ public void draw(Canvas canvas) {
if (mRanFinishAnimation) {
- super.drawInternal(canvas, bounds);
+ super.draw(canvas);
return;
}
@@ -172,13 +172,15 @@
mProgressPaint.setColor(mIndicatorColor);
mProgressPaint.setAlpha(mTrackAlpha);
if (mShadowBitmap != null) {
- canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint);
+ canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
}
canvas.drawPath(mScaledProgressPath, mProgressPaint);
- int saveCount = canvas.save();
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ Rect bounds = getBounds();
+
canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
- super.drawInternal(canvas, bounds);
+ super.draw(canvas);
canvas.restoreToCount(saveCount);
}
diff --git a/src/com/android/launcher3/graphics/ViewScrim.java b/src/com/android/launcher3/graphics/ViewScrim.java
deleted file mode 100644
index e1727e0..0000000
--- a/src/com/android/launcher3/graphics/ViewScrim.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.graphics;
-
-import android.graphics.Canvas;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewParent;
-
-import com.android.launcher3.R;
-
-/**
- * A utility class that can be used to draw a scrim behind a view
- */
-public abstract class ViewScrim<T extends View> {
-
- public static Property<ViewScrim, Float> PROGRESS =
- new Property<ViewScrim, Float>(Float.TYPE, "progress") {
- @Override
- public Float get(ViewScrim viewScrim) {
- return viewScrim.mProgress;
- }
-
- @Override
- public void set(ViewScrim object, Float value) {
- object.setProgress(value);
- }
- };
-
- protected final T mView;
- protected float mProgress = 0;
-
- public ViewScrim(T view) {
- mView = view;
- }
-
- public void attach() {
- mView.setTag(R.id.view_scrim, this);
- }
-
- public void setProgress(float progress) {
- if (mProgress != progress) {
- mProgress = progress;
- onProgressChanged();
- invalidate();
- }
- }
-
- public abstract void draw(Canvas canvas, int width, int height);
-
- protected void onProgressChanged() { }
-
- public void invalidate() {
- ViewParent parent = mView.getParent();
- if (parent != null) {
- ((View) parent).invalidate();
- }
- }
-
- public static ViewScrim get(View view) {
- return (ViewScrim) view.getTag(R.id.view_scrim);
- }
-}
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
deleted file mode 100644
index 2318a77..0000000
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.graphics;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Shader;
-import android.support.v4.graphics.ColorUtils;
-import android.util.DisplayMetrics;
-import android.view.View;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.dynamicui.WallpaperColorInfo;
-
-/**
- * View scrim which draws behind hotseat and workspace
- */
-public class WorkspaceAndHotseatScrim extends ViewScrim<Workspace> implements
- View.OnAttachStateChangeListener, WallpaperColorInfo.OnChangeListener {
-
- private static final int DARK_SCRIM_COLOR = 0x55000000;
- private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
- private static final int ALPHA_MASK_HEIGHT_DP = 500;
- private static final int ALPHA_MASK_BITMAP_DP = 200;
- private static final int ALPHA_MASK_WIDTH_DP = 2;
-
- private final Rect mHighlightRect = new Rect();
- private final Launcher mLauncher;
- private final WallpaperColorInfo mWallpaperColorInfo;
-
- private final boolean mHasHotseatScrim;
- private final RectF mFinalMaskRect = new RectF();
- private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-
- private final Bitmap mBottomMask;
- private final int mMaskHeight;
-
- private int mFullScrimColor;
-
- private final int mMaxAlpha;
- private int mAlpha = 0;
-
- public WorkspaceAndHotseatScrim(Workspace view) {
- super(view);
- mLauncher = Launcher.getLauncher(view.getContext());
- mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
-
- mMaxAlpha = mLauncher.getResources().getInteger(R.integer.config_workspaceScrimAlpha);
- mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_BITMAP_DP,
- view.getResources().getDisplayMetrics());
-
- mHasHotseatScrim = !mWallpaperColorInfo.supportsDarkText();
- mBottomMask = mHasHotseatScrim ? createDitheredAlphaMask() : null;
-
- view.addOnAttachStateChangeListener(this);
- onExtractedColorsChanged(mWallpaperColorInfo);
- }
-
- @Override
- public void draw(Canvas canvas, int width, int height) {
- // Draw the background below children.
- if (mAlpha > 0) {
- // Update the scroll position first to ensure scrim cutout is in the right place.
- mView.computeScrollWithoutInvalidation();
- CellLayout currCellLayout = mView.getCurrentDragOverlappingLayout();
- canvas.save();
- if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
- // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
- mLauncher.getDragLayer()
- .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
- canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
- }
-
- canvas.drawColor(ColorUtils.setAlphaComponent(mFullScrimColor, mAlpha));
- canvas.restore();
- }
-
- if (mHasHotseatScrim && !mLauncher.getDeviceProfile().isVerticalBarLayout()) {
- mFinalMaskRect.set(0, height - mMaskHeight, width, height);
- mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * (1 - mProgress)));
- canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint);
- }
- }
-
- @Override
- protected void onProgressChanged() {
- mAlpha = Math.round(mMaxAlpha * mProgress);
- }
-
- @Override
- public void onViewAttachedToWindow(View view) {
- mWallpaperColorInfo.addOnChangeListener(this);
- onExtractedColorsChanged(mWallpaperColorInfo);
- }
-
- @Override
- public void onViewDetachedFromWindow(View view) {
- mWallpaperColorInfo.removeOnChangeListener(this);
- }
-
- @Override
- public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
- // for super light wallpaper it needs to be darken for contrast to workspace
- // for dark wallpapers the text is white so darkening works as well
- mFullScrimColor = ColorUtils.compositeColors(DARK_SCRIM_COLOR,
- wallpaperColorInfo.getMainColor());
- mBottomMaskPaint.setColor(mFullScrimColor);
- }
-
- public Bitmap createDitheredAlphaMask() {
- DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
- int width = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
- int gradientHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
- Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8);
- Canvas c = new Canvas(dst);
- Paint paint = new Paint(Paint.DITHER_FLAG);
- LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight,
- new int[]{
- 0x00FFFFFF,
- ColorUtils.setAlphaComponent(Color.WHITE, (int) (0xFF * 0.95)),
- 0xFFFFFFFF},
- new float[]{0f, 0.8f, 1f},
- Shader.TileMode.CLAMP);
- paint.setShader(lg);
- c.drawRect(0, 0, width, gradientHeight, paint);
- return dst;
- }
-}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index d68ac15..c608a23 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -20,8 +20,11 @@
import android.view.View;
import com.android.launcher3.ButtonDropTarget;
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.InfoDropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.UninstallDropTarget;
import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -162,8 +165,12 @@
return newTarget(Target.Type.CONTAINER);
}
Target t = newTarget(Target.Type.CONTROL);
- if (v instanceof ButtonDropTarget) {
- t.controlType = ((ButtonDropTarget) v).getControlTypeForLogging();
+ if (v instanceof InfoDropTarget) {
+ t.controlType = ControlType.APPINFO_TARGET;
+ } else if (v instanceof UninstallDropTarget) {
+ t.controlType = ControlType.UNINSTALL_TARGET;
+ } else if (v instanceof DeleteDropTarget) {
+ t.controlType = ControlType.REMOVE_TARGET;
}
return t;
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index fefe07d..a33a039 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -17,7 +17,10 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.LongSparseArray;
import android.util.Pair;
import com.android.launcher3.AllAppsList;
@@ -34,6 +37,7 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
import java.util.ArrayList;
import java.util.List;
@@ -60,6 +64,7 @@
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
+ ArrayMap<UserHandle, UserFolderInfo> userFolderMap = new ArrayMap<>();
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
@@ -82,6 +87,21 @@
if (item instanceof AppInfo) {
item = ((AppInfo) item).makeShortcut();
}
+
+ if (!Process.myUserHandle().equals(item.user)) {
+ // Check if this belongs to a work folder.
+ if (!(entry.second instanceof LauncherActivityInfo)) {
+ continue;
+ }
+
+ UserFolderInfo userFolderInfo = userFolderMap.get(item.user);
+ if (userFolderInfo == null) {
+ userFolderInfo = new UserFolderInfo(context, item.user, dataModel);
+ userFolderMap.put(item.user, userFolderInfo);
+ }
+ item = userFolderInfo.convertToWorkspaceItem(
+ (ShortcutInfo) item, (LauncherActivityInfo) entry.second);
+ }
}
if (item != null) {
filteredItems.add(item);
@@ -140,6 +160,10 @@
}
});
}
+
+ for (UserFolderInfo userFolderInfo : userFolderMap.values()) {
+ userFolderInfo.applyPendingState(getModelWriter());
+ }
}
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index fcdc088..9aa30e7 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -29,6 +29,7 @@
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -79,18 +80,19 @@
*/
public final void scheduleCallbackTask(final CallbackTask task) {
final Callbacks callbacks = mModel.getCallback();
- mUiExecutor.execute(() -> {
- Callbacks cb = mModel.getCallback();
- if (callbacks == cb && cb != null) {
- task.execute(callbacks);
+ mUiExecutor.execute(new Runnable() {
+ public void run() {
+ Callbacks cb = mModel.getCallback();
+ if (callbacks == cb && cb != null) {
+ task.execute(callbacks);
+ }
}
});
}
public ModelWriter getModelWriter() {
- // Updates from model task, do not deal with icon position in hotseat. Also no need to
- // verify changes as the ModelTasks always push the changes to callbacks
- return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
+ // Updates from model task, do not deal with icon position in hotseat.
+ return mModel.getWriter(false /* hasVerticalHotseat */);
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index fff1e69..8640401 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -106,11 +106,6 @@
public final WidgetsModel widgetsModel = new WidgetsModel();
/**
- * Id when the model was last bound
- */
- public int lastBindId = 0;
-
- /**
* Clears all the data
*/
public synchronized void clear() {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 5d4a352..5acc790 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -25,6 +25,7 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.MainThreadExecutor;
@@ -36,6 +37,7 @@
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -98,7 +100,6 @@
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
- mBgDataModel.lastBindId++;
}
final int currentScreen;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 9d1ff83..00dd3aa 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,11 +16,6 @@
package com.android.launcher3.model;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -30,6 +25,7 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageInstaller;
import android.graphics.Bitmap;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
@@ -40,6 +36,7 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.ClickShadowView;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.IconCache;
import com.android.launcher3.InstallShortcutReceiver;
@@ -65,6 +62,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LooperIdleLock;
+import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Provider;
@@ -78,6 +76,11 @@
import java.util.Map;
import java.util.concurrent.CancellationException;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
/**
* Runnable for the thread that loads the contents of the launcher:
* - workspace icons
@@ -144,7 +147,9 @@
TraceHelper.beginSection(TAG);
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
+ TraceHelper.partitionSection(TAG, "step 1.1: loading UI resources");
+ loadUiResources();
+ TraceHelper.partitionSection(TAG, "step 1.2: loading workspace");
loadWorkspace();
verifyNotStopped();
@@ -207,6 +212,15 @@
this.notify();
}
+ public void loadUiResources() {
+ if (Utilities.ATLEAST_OREO) {
+ LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+ ClickShadowView.setAdaptiveIconScaleFactor(li.getNormalizer()
+ .getScale(new AdaptiveIconDrawable(null, null), null, null, null));
+ li.recycle();
+ }
+ }
+
private void loadWorkspace() {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
@@ -798,6 +812,8 @@
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
}
+
+ ManagedProfileHeuristic.onAllAppsLoaded(mApp.getContext(), apps, user);
}
if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 72c703b..032ed78 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -21,21 +21,17 @@
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
import android.util.Log;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LooperExecutor;
@@ -50,26 +46,17 @@
public class ModelWriter {
private static final String TAG = "ModelWriter";
- public static final boolean DEBUG_DELETE = true;
private final Context mContext;
- private final LauncherModel mModel;
private final BgDataModel mBgDataModel;
- private final Handler mUiHandler;
-
private final Executor mWorkerExecutor;
private final boolean mHasVerticalHotseat;
- private final boolean mVerifyChanges;
- public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
- boolean hasVerticalHotseat, boolean verifyChanges) {
+ public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
mContext = context;
- mModel = model;
mBgDataModel = dataModel;
mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
mHasVerticalHotseat = hasVerticalHotseat;
- mVerifyChanges = verifyChanges;
- mUiHandler = new Handler(Looper.getMainLooper());
}
private void updateItemInfoProps(
@@ -225,16 +212,15 @@
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
writer.put(Favorites._ID, item.id);
- ModelVerifier verifier = new ModelVerifier();
-
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- mWorkerExecutor.execute(() -> {
- cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
- synchronized (mBgDataModel) {
- checkItemInfoLocked(item.id, item, stackTrace);
- mBgDataModel.addItem(mContext, item, true);
- verifier.verifyModel();
+ synchronized (mBgDataModel) {
+ checkItemInfoLocked(item.id, item, stackTrace);
+ mBgDataModel.addItem(mContext, item, true);
+ }
}
});
}
@@ -257,23 +243,14 @@
* Removes the specified items from the database
*/
public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
- if (DEBUG_DELETE) {
- // Log it on the colling thread to get the proper stack trace
- FileLog.d(TAG, "Starting item deletion", new Exception());
- for (ItemInfo item : items) {
- FileLog.d(TAG, "deleting item " + item);
- }
- FileLog.d(TAG, "Finished deleting items");
- }
- ModelVerifier verifier = new ModelVerifier();
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ for (ItemInfo item : items) {
+ final Uri uri = Favorites.getContentUri(item.id);
+ mContext.getContentResolver().delete(uri, null, null);
- mWorkerExecutor.execute(() -> {
- for (ItemInfo item : items) {
- final Uri uri = Favorites.getContentUri(item.id);
- mContext.getContentResolver().delete(uri, null, null);
-
- mBgDataModel.removeItem(mContext, item);
- verifier.verifyModel();
+ mBgDataModel.removeItem(mContext, item);
+ }
}
});
}
@@ -282,23 +259,17 @@
* Remove the specified folder and all its contents from the database.
*/
public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
- if (DEBUG_DELETE) {
- // Log it on the colling thread to get the proper stack trace
- FileLog.d(TAG, "Deleting folder " + info, new Exception());
- }
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.delete(LauncherSettings.Favorites.CONTENT_URI,
+ LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+ mBgDataModel.removeItem(mContext, info.contents);
+ info.contents.clear();
- ModelVerifier verifier = new ModelVerifier();
-
- mWorkerExecutor.execute(() -> {
- ContentResolver cr = mContext.getContentResolver();
- cr.delete(LauncherSettings.Favorites.CONTENT_URI,
- LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
- mBgDataModel.removeItem(mContext, info.contents);
- info.contents.clear();
-
- cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
- mBgDataModel.removeItem(mContext, info);
- verifier.verifyModel();
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+ mBgDataModel.removeItem(mContext, info);
+ }
});
}
@@ -353,7 +324,6 @@
private abstract class UpdateItemBaseRunnable implements Runnable {
private final StackTraceElement[] mStackTrace;
- private final ModelVerifier mVerifier = new ModelVerifier();
UpdateItemBaseRunnable() {
mStackTrace = new Throwable().getStackTrace();
@@ -398,45 +368,7 @@
} else {
mBgDataModel.workspaceItems.remove(modelItem);
}
- mVerifier.verifyModel();
}
}
}
-
- /**
- * Utility class to verify model updates are propagated properly to the callback.
- */
- public class ModelVerifier {
-
- final int startId;
-
- ModelVerifier() {
- startId = mBgDataModel.lastBindId;
- }
-
- void verifyModel() {
- if (!mVerifyChanges || mModel.getCallback() == null) {
- return;
- }
-
- int executeId = mBgDataModel.lastBindId;
-
- mUiHandler.post(() -> {
- int currentId = mBgDataModel.lastBindId;
- if (currentId > executeId) {
- // Model was already bound after job was executed.
- return;
- }
- if (executeId == startId) {
- // Bound model has not changed during the job
- return;
- }
- // Bound model was changed between submitting the job and executing the job
- Callbacks callbacks = mModel.getCallback();
- if (callbacks != null) {
- callbacks.rebindModel();
- }
- });
- }
- }
}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 9f8f263..1ff0dac 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -1,8 +1,6 @@
package com.android.launcher3.model;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
-
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -155,11 +153,6 @@
// add and update.
for (WidgetItem item : rawWidgetsShortcuts) {
if (item.widgetInfo != null) {
- if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
- // Widget is hidden from picker
- continue;
- }
-
// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 1fd7078..cbdabf3 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -74,9 +74,6 @@
/** Maps keys to their corresponding current group key */
private final Map<String, String> mNotificationGroupKeyMap = new HashMap<>();
- /** The last notification key that was dismissed from launcher UI */
- private String mLastKeyDismissedByLauncher;
-
private SettingsObserver mNotificationBadgingObserver;
private final Handler.Callback mWorkerCallback = new Handler.Callback() {
@@ -254,25 +251,13 @@
}
NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
- String key = sbn.getKey();
if (notificationGroup != null) {
- notificationGroup.removeChildKey(key);
+ notificationGroup.removeChildKey(sbn.getKey());
if (notificationGroup.isEmpty()) {
- if (key.equals(mLastKeyDismissedByLauncher)) {
- // Only cancel the group notification if launcher dismissed the last child.
- cancelNotification(notificationGroup.getGroupSummaryKey());
- }
+ cancelNotification(notificationGroup.getGroupSummaryKey());
mNotificationGroupMap.remove(sbn.getGroupKey());
}
}
- if (key.equals(mLastKeyDismissedByLauncher)) {
- mLastKeyDismissedByLauncher = null;
- }
- }
-
- public void cancelNotificationFromLauncher(String key) {
- mLastKeyDismissedByLauncher = key;
- cancelNotification(key);
}
@Override
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index e427a81..b3ef7bb 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -77,7 +77,6 @@
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Themes;
@@ -908,9 +907,12 @@
@Override
public boolean onLongClick(View v) {
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
// Return early if not the correct view
if (!(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
// Long clicked on a shortcut.
DeepShortcutView sv = (DeepShortcutView) v.getParent();
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index f1b8ec0..335426c 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -206,7 +206,7 @@
if (notificationListener == null) {
return;
}
- notificationListener.cancelNotificationFromLauncher(notificationKey);
+ notificationListener.cancelNotification(notificationKey);
}
public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 2cc8dfa..32fd063 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -9,6 +9,7 @@
import android.view.View;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.InfoDropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
@@ -82,8 +83,7 @@
public void onClick(View view) {
Rect sourceBounds = launcher.getViewBounds(view);
Bundle opts = launcher.getActivityLaunchOptionsAsBundle(view, false);
- new PackageManagerHelper(launcher).startDetailsActivityForInfo(
- itemInfo, sourceBounds, opts);
+ InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, sourceBounds, opts);
launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.APPINFO_TARGET, view);
}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9ad266b..450a690 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -30,7 +30,6 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.touch.ItemClickHandler;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -121,7 +120,7 @@
mBubbleText.setText(usingLongLabel ? longLabel : mDetail.getShortLabel());
// TODO: Add the click handler to this view directly and not the child view.
- mBubbleText.setOnClickListener(ItemClickHandler.INSTANCE);
+ mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
mBubbleText.setOnLongClickListener(container);
mBubbleText.setOnTouchListener(container);
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index ee97641..cfb9258 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -50,10 +50,12 @@
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(b);
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
canvas.translate(bounds.left, bounds.top);
d.draw(canvas);
+ canvas.restore();
return b;
}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index 0a2c3e4..d3c0fef 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -56,8 +56,8 @@
sScheduler.schedule(this);
}
- public boolean clearReference() {
- return sScheduler.clearReference(this);
+ public void clearReference() {
+ sScheduler.clearReference(this);
}
public static boolean handleCreate(Launcher launcher, Intent intent) {
@@ -125,12 +125,10 @@
return false;
}
- public synchronized boolean clearReference(InternalStateHandler handler) {
+ public synchronized void clearReference(InternalStateHandler handler) {
if (mPendingHandler.get() == handler) {
mPendingHandler.clear();
- return true;
}
- return false;
}
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
deleted file mode 100644
index 8f83648..0000000
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.states;
-
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
-import static android.provider.Settings.System.getUriFor;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-import com.android.launcher3.R;
-
-/**
- * Utility class to manage launcher rotation
- */
-public class RotationHelper extends ContentObserver {
-
- public static final int REQUEST_NONE = 0;
- public static final int REQUEST_ROTATE = 1;
- public static final int REQUEST_LOCK = 2;
-
- private final Activity mActivity;
- private final ContentResolver mCr;
-
- private final boolean mIgnoreAutoRotateSettings;
- private boolean mAutoRotateEnabled;
-
- /**
- * Rotation request made by {@link InternalStateHandler}. This supersedes any other request.
- */
- private int mStateHandlerRequest = REQUEST_NONE;
- /**
- * Rotation request made by a Launcher State
- */
- private int mCurrentStateRequest = REQUEST_NONE;
-
- // This is used to defer setting rotation flags until the activity is being created
- private boolean mInitialized;
- public boolean mDestroyed;
-
- private int mLastActivityFlags = -1;
-
- public RotationHelper(Activity activity) {
- super(new Handler());
- mActivity = activity;
-
- // On large devices we do not handle auto-rotate differently.
- mIgnoreAutoRotateSettings = mActivity.getResources().getBoolean(R.bool.allow_rotation);
- if (!mIgnoreAutoRotateSettings) {
- mCr = mActivity.getContentResolver();
- mCr.registerContentObserver(getUriFor(ACCELEROMETER_ROTATION), false, this);
- mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
- } else {
- mCr = null;
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
- notifyChange();
- }
-
- public void setStateHandlerRequest(int request) {
- if (mStateHandlerRequest != request) {
- mStateHandlerRequest = request;
- notifyChange();
- }
- }
-
- public void setCurrentStateRequest(int request) {
- if (mCurrentStateRequest != request) {
- mCurrentStateRequest = request;
- notifyChange();
- }
- }
-
- public void initialize() {
- if (!mInitialized) {
- mInitialized = true;
- notifyChange();
- }
- }
-
- public void destroy() {
- if (!mDestroyed) {
- mDestroyed = true;
- if (mCr != null) {
- mCr.unregisterContentObserver(this);
- }
- }
- }
-
- private void notifyChange() {
- if (!mInitialized || mDestroyed) {
- return;
- }
-
- final int activityFlags;
- if (mStateHandlerRequest != REQUEST_NONE) {
- activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
- SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
- } else if (mCurrentStateRequest == REQUEST_LOCK) {
- activityFlags = SCREEN_ORIENTATION_LOCKED;
- } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE) {
- activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
- } else if (mAutoRotateEnabled) {
- // If auto rotation is on, lock to device orientation
- activityFlags = SCREEN_ORIENTATION_NOSENSOR;
- } else {
- // If auto rotation is off, allow rotation on the activity, in case the user is using
- // forced rotation.
- activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
- }
- if (activityFlags != mLastActivityFlags) {
- mLastActivityFlags = activityFlags;
- mActivity.setRequestedOrientation(mLastActivityFlags);
- }
- }
-}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 89a9e2d..6d584cd 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -16,9 +16,10 @@
package com.android.launcher3.states;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
+import android.os.Handler;
import android.view.View;
import com.android.launcher3.DeviceProfile;
@@ -35,7 +36,11 @@
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED |
- FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS;
+ FLAG_DISABLE_PAGE_CLIPPING;
+
+ // Determines how long to wait after a rotation before restoring the screen orientation to
+ // match the sensor state.
+ private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
public SpringLoadedState(int id) {
super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, STATE_FLAGS);
@@ -79,16 +84,30 @@
ws.showPageIndicatorAtCurrentScroll();
ws.getPageIndicator().setShouldAutoHide(false);
+ // Lock the orientation:
+ if (launcher.isRotationEnabled()) {
+ launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ }
+
// Prevent any Un/InstallShortcutReceivers from updating the db while we are
// in spring loaded mode
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
}
@Override
public void onStateDisabled(final Launcher launcher) {
launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);
+ // Unlock rotation lock
+ if (launcher.isRotationEnabled()) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ }, RESTORE_SCREEN_ORIENTATION_DELAY);
+ }
+
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
deleted file mode 100644
index f2f5592..0000000
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.touch;
-
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
-import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
-
-import android.app.AlertDialog;
-import android.content.Intent;
-import android.os.Process;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Toast;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.PromiseAppInfo;
-import com.android.launcher3.R;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-import com.android.launcher3.widget.WidgetAddFlowHandler;
-
-/**
- * Class for handling clicks on workspace and all-apps items
- */
-public class ItemClickHandler {
-
- /**
- * Instance used for click handling on items
- */
- public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
-
- private static void onClick(View v) {
- // Make sure that rogue clicks don't get through while allapps is launching, or after the
- // view has detached (it's possible for this to happen if the view is removed mid touch).
- if (v.getWindowToken() == null) {
- return;
- }
-
- Launcher launcher = Launcher.getLauncher(v.getContext());
- if (!launcher.getWorkspace().isFinishedSwitchingState()) {
- return;
- }
-
- Object tag = v.getTag();
- if (tag instanceof ShortcutInfo) {
- onClickAppShortcut(v, (ShortcutInfo) tag, launcher);
- } else if (tag instanceof FolderInfo) {
- if (v instanceof FolderIcon) {
- onClickFolderIcon(v);
- }
- } else if (tag instanceof AppInfo) {
- startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
- } else if (tag instanceof LauncherAppWidgetInfo) {
- if (v instanceof PendingAppWidgetHostView) {
- onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
- }
- }
- }
-
- /**
- * Event handler for a folder icon click.
- *
- * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
- */
- private static void onClickFolderIcon(View v) {
- Folder folder = ((FolderIcon) v).getFolder();
- if (!folder.isOpen() && !folder.isDestroyed()) {
- // Open the requested folder
- folder.animateOpen();
- }
- }
-
- /**
- * Event handler for the app widget view which has not fully restored.
- */
- private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
- if (launcher.getPackageManager().isSafeMode()) {
- Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
- return;
- }
-
- final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
- if (v.isReadyForClickSetup()) {
- LauncherAppWidgetProviderInfo appWidgetInfo = AppWidgetManagerCompat
- .getInstance(launcher).findProvider(info.providerName, info.user);
- if (appWidgetInfo == null) {
- return;
- }
- WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
-
- if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
- // This should not happen, as we make sure that an Id is allocated during bind.
- return;
- }
- addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
- REQUEST_BIND_PENDING_APPWIDGET);
- } else {
- addFlowHandler.startConfigActivity(launcher, info, REQUEST_RECONFIGURE_APPWIDGET);
- }
- } else {
- final String packageName = info.providerName.getPackageName();
- onClickPendingAppItem(v, launcher, packageName, info.installProgress >= 0);
- }
- }
-
- private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
- boolean downloadStarted) {
- if (downloadStarted) {
- // If the download has started, simply direct to the market app.
- startMarketIntentForPackage(v, launcher, packageName);
- return;
- }
- new AlertDialog.Builder(launcher)
- .setTitle(R.string.abandoned_promises_title)
- .setMessage(R.string.abandoned_promise_explanation)
- .setPositiveButton(R.string.abandoned_search,
- (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
- .setNeutralButton(R.string.abandoned_clean_this,
- (d, i) -> launcher.getWorkspace()
- .removeAbandonedPromise(packageName, Process.myUserHandle()))
- .create().show();
- }
-
- private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
- ItemInfo item = (ItemInfo) v.getTag();
- Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
- launcher.startActivitySafely(v, intent, item);
- }
-
- /**
- * Event handler for an app shortcut click.
- *
- * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
- */
- private static void onClickAppShortcut(View v, ShortcutInfo shortcut, Launcher launcher) {
- if (shortcut.isDisabled()) {
- final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
- if ((disabledFlags &
- ~FLAG_DISABLED_SUSPENDED &
- ~FLAG_DISABLED_QUIET_USER) == 0) {
- // If the app is only disabled because of the above flags, launch activity anyway.
- // Framework will tell the user why the app is suspended.
- } else {
- if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
- // Use a message specific to this shortcut, if it has one.
- Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
- return;
- }
- // Otherwise just use a generic error message.
- int error = R.string.activity_not_available;
- if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
- error = R.string.safemode_shortcut_error;
- } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
- (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
- error = R.string.shortcut_not_available;
- }
- Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
- return;
- }
- }
-
- // Check for abandoned promise
- if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
- String packageName = shortcut.intent.getComponent() != null ?
- shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
- if (!TextUtils.isEmpty(packageName)) {
- onClickPendingAppItem(v, launcher, packageName,
- shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
- return;
- }
- }
-
- // Start activities
- startAppShortcutOrInfoActivity(v, shortcut, launcher);
- }
-
- private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
- Intent intent;
- if (item instanceof PromiseAppInfo) {
- PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
- intent = promiseAppInfo.getMarketIntent(launcher);
- } else {
- intent = item.getIntent();
- }
- if (intent == null) {
- throw new IllegalArgumentException("Input must have a valid intent");
- }
- if (item instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) item;
- if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
- && intent.getAction() == Intent.ACTION_VIEW) {
- // make a copy of the intent that has the package set to null
- // we do this because the platform sometimes disables instant
- // apps temporarily (triggered by the user) and fallbacks to the
- // web ui. This only works though if the package isn't set
- intent = new Intent(intent);
- intent.setPackage(null);
- }
- }
- launcher.startActivitySafely(v, intent, item);
- }
-}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
deleted file mode 100644
index f10a695..0000000
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.touch;
-
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.view.View;
-import android.view.View.OnLongClickListener;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
-
-/**
- * Class to handle long-clicks on workspace items and start drag as a result.
- */
-public class ItemLongClickListener {
-
- public static OnLongClickListener INSTANCE_WORKSPACE =
- ItemLongClickListener::onWorkspaceItemLongClick;
-
- public static OnLongClickListener INSTANCE_ALL_APPS =
- ItemLongClickListener::onAllAppsItemLongClick;
-
- private static boolean onWorkspaceItemLongClick(View v) {
- Launcher launcher = Launcher.getLauncher(v.getContext());
- if (!canStartDrag(launcher)) return false;
- if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
- if (!(v.getTag() instanceof ItemInfo)) return false;
-
- launcher.setWaitingForResult(null);
- beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
- return true;
- }
-
- public static void beginDrag(View v, Launcher launcher, ItemInfo info,
- DragOptions dragOptions) {
- if (info.container >= 0) {
- Folder folder = Folder.getOpen(launcher);
- if (folder != null) {
- if (!folder.getItemsInReadingOrder().contains(v)) {
- folder.close(true);
- } else {
- folder.startDrag(v, dragOptions);
- return;
- }
- }
- }
-
- CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
- launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
- }
-
- private static boolean onAllAppsItemLongClick(View v) {
- Launcher launcher = Launcher.getLauncher(v.getContext());
- if (!canStartDrag(launcher)) return false;
- // When we have exited all apps or are in transition, disregard long clicks
- if (!launcher.isInState(LauncherState.ALL_APPS) ||
- launcher.getWorkspace().isSwitchingState()) return false;
-
- // Start the drag
- final DragController dragController = launcher.getDragController();
- dragController.addDragListener(new DragController.DragListener() {
- @Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- v.setVisibility(INVISIBLE);
- }
-
- @Override
- public void onDragEnd() {
- v.setVisibility(VISIBLE);
- dragController.removeDragListener(this);
- }
- });
-
- DeviceProfile grid = launcher.getDeviceProfile();
- DragOptions options = new DragOptions();
- options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
- launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), options);
- return false;
- }
-
- public static boolean canStartDrag(Launcher launcher) {
- if (launcher == null) {
- return false;
- }
- // We prevent dragging when we are loading the workspace as it is possible to pick up a view
- // that is subsequently removed from the workspace in startBinding().
- if (launcher.isWorkspaceLocked()) return false;
- // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
- if (launcher.getDragController().isDragging()) return false;
-
- return true;
- }
-}
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
deleted file mode 100644
index df11686..0000000
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.touch;
-
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.ViewConfiguration.getLongPressTimeout;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-/**
- * Helper class to handle touch on empty space in workspace and show options popup on long press
- */
-public class WorkspaceTouchListener implements OnTouchListener, Runnable {
-
- /**
- * STATE_PENDING_PARENT_INFORM is the state between longPress performed & the next motionEvent.
- * This next event is used to send an ACTION_CANCEL to Workspace, to that it clears any
- * temporary scroll state. After that, the state is set to COMPLETED, and we just eat up all
- * subsequent motion events.
- */
- private static final int STATE_CANCELLED = 0;
- private static final int STATE_REQUESTED = 1;
- private static final int STATE_PENDING_PARENT_INFORM = 2;
- private static final int STATE_COMPLETED = 3;
-
- private final Rect mTempRect = new Rect();
- private final Launcher mLauncher;
- private final Workspace mWorkspace;
- private final PointF mTouchDownPoint = new PointF();
-
- private int mLongPressState = STATE_CANCELLED;
-
- public WorkspaceTouchListener(Launcher launcher, Workspace workspace) {
- mLauncher = launcher;
- mWorkspace = workspace;
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent ev) {
- int action = ev.getActionMasked();
- if (action == ACTION_DOWN) {
- // Check if we can handle long press.
- boolean handleLongPress = AbstractFloatingView.getTopOpenView(mLauncher) == null
- && mLauncher.isInState(NORMAL);
-
- if (handleLongPress) {
- // Check if the event is not near the edges
- DeviceProfile dp = mLauncher.getDeviceProfile();
- DragLayer dl = mLauncher.getDragLayer();
- Rect insets = dp.getInsets();
-
- mTempRect.set(insets.left, insets.top, dl.getWidth() - insets.right,
- dl.getHeight() - insets.bottom);
- mTempRect.inset(dp.edgeMarginPx, dp.edgeMarginPx);
- handleLongPress = mTempRect.contains((int) ev.getX(), (int) ev.getY());
- }
-
- cancelLongPress();
- if (handleLongPress) {
- mLongPressState = STATE_REQUESTED;
- mTouchDownPoint.set(ev.getX(), ev.getY());
- mWorkspace.postDelayed(this, getLongPressTimeout());
- }
-
- mWorkspace.onTouchEvent(ev);
- // Return true to keep receiving touch events
- return true;
- }
-
- if (mLongPressState == STATE_PENDING_PARENT_INFORM) {
- // Inform the workspace to cancel touch handling
- ev.setAction(ACTION_CANCEL);
- mWorkspace.onTouchEvent(ev);
- ev.setAction(action);
- mLongPressState = STATE_COMPLETED;
- }
-
- if (mLongPressState == STATE_COMPLETED) {
- // We have handled the touch, so workspace does not need to know anything anymore.
- return true;
- } else if (mLongPressState == STATE_REQUESTED) {
- mWorkspace.onTouchEvent(ev);
- if (action == ACTION_UP || action == ACTION_CANCEL || mWorkspace.isHandlingTouch()) {
- cancelLongPress();
- }
- return true;
- } else {
- // We don't want to handle touch, let workspace handle it as usual.
- return false;
- }
- }
-
- private void cancelLongPress() {
- mWorkspace.removeCallbacks(this);
- mLongPressState = STATE_CANCELLED;
- }
-
- @Override
- public void run() {
- if (mLongPressState == STATE_REQUESTED) {
- mLongPressState = STATE_PENDING_PARENT_INFORM;
- mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
-
- mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
- Action.Direction.NONE, ContainerType.WORKSPACE,
- mWorkspace.getCurrentPage());
- OptionsPopupView.show(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
- }
- }
-}
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index b793f54..b80e94d 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -177,10 +177,7 @@
}
int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
- int x = invert ? (m - cx - 1) : cx;
- if (x < m && cy < n) { // check if view fits into matrix, else skip
- matrix[x][cy] = i;
- }
+ matrix[invert ? (m - cx - 1) : cx][cy] = i;
}
if (DEBUG) {
printMatrix(matrix);
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
new file mode 100644
index 0000000..009aee7
--- /dev/null
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.SessionCommitReceiver;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.ModelWriter;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Handles addition of app shortcuts for managed profiles.
+ * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
+ */
+public class ManagedProfileHeuristic {
+
+ private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
+
+ /**
+ * Duration (in milliseconds) for which app shortcuts will be added to work folder.
+ */
+ private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
+
+ public static void onAllAppsLoaded(final Context context,
+ List<LauncherActivityInfo> apps, UserHandle user) {
+ if (Process.myUserHandle().equals(user)) {
+ return;
+ }
+
+ UserFolderInfo ufi = new UserFolderInfo(context, user, null);
+ // We only handle folder creation once. Later icon additions are handled using package
+ // or session events.
+ if (ufi.folderAlreadyCreated) {
+ return;
+ }
+
+ if (Utilities.ATLEAST_OREO && !SessionCommitReceiver.isEnabled(context)) {
+ // Just mark the folder id preference to avoid new folder creation later.
+ ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply();
+ return;
+ }
+
+ InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_BULK_ADD);
+ for (LauncherActivityInfo app : apps) {
+ // Queue all items which should go in the work folder.
+ if (app.getFirstInstallTime() < ufi.addIconToFolderTime) {
+ InstallShortcutReceiver.queueActivityInfo(app, context);
+ }
+ }
+ // Post the queue update on next frame, so that the loader gets finished.
+ new Handler(LauncherModel.getWorkerLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ InstallShortcutReceiver.disableAndFlushInstallQueue(
+ InstallShortcutReceiver.FLAG_BULK_ADD, context);
+ }
+ });
+ }
+
+
+ /**
+ * Utility class to help workspace icon addition.
+ */
+ public static class UserFolderInfo {
+
+ final ArrayList<ShortcutInfo> pendingShortcuts = new ArrayList<>();
+
+ final UserHandle user;
+
+ final long userSerial;
+ // Time until which icons will be added to folder instead.
+ final long addIconToFolderTime;
+
+ final String folderIdKey;
+ final SharedPreferences prefs;
+
+ final boolean folderAlreadyCreated;
+ final FolderInfo folderInfo;
+
+ boolean folderPendingAddition;
+
+ public UserFolderInfo(Context context, UserHandle user, BgDataModel dataModel) {
+ this.user = user;
+
+ UserManagerCompat um = UserManagerCompat.getInstance(context);
+ userSerial = um.getSerialNumberForUser(user);
+ addIconToFolderTime = um.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
+
+ folderIdKey = USER_FOLDER_ID_PREFIX + userSerial;
+ prefs = prefs(context);
+
+ folderAlreadyCreated = prefs.contains(folderIdKey);
+ if (dataModel != null) {
+ if (folderAlreadyCreated) {
+ long folderId = prefs.getLong(folderIdKey, ItemInfo.NO_ID);
+ folderInfo = dataModel.folders.get(folderId);
+ } else {
+ folderInfo = new FolderInfo();
+ folderInfo.title = context.getText(R.string.work_folder_name);
+ folderInfo.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
+ folderPendingAddition = true;
+ }
+ } else {
+ folderInfo = null;
+ }
+ }
+
+ /**
+ * Returns the ItemInfo which should be added to the workspace. In case the the provided
+ * {@link ShortcutInfo} or a wrapped {@link FolderInfo} or null.
+ */
+ public ItemInfo convertToWorkspaceItem(
+ ShortcutInfo shortcut, LauncherActivityInfo activityInfo) {
+ if (activityInfo.getFirstInstallTime() >= addIconToFolderTime) {
+ return shortcut;
+ }
+
+ if (folderAlreadyCreated) {
+ if (folderInfo == null) {
+ // Work folder was deleted by user, add icon to home screen.
+ return shortcut;
+ } else {
+ // Add item to work folder instead. Nothing needs to be added
+ // on the homescreen.
+ pendingShortcuts.add(shortcut);
+ return null;
+ }
+ }
+
+ pendingShortcuts.add(shortcut);
+ folderInfo.add(shortcut, false);
+ if (folderPendingAddition) {
+ folderPendingAddition = false;
+ return folderInfo;
+ } else {
+ // WorkFolder already requested to be added. Nothing new needs to be added.
+ return null;
+ }
+ }
+
+ public void applyPendingState(ModelWriter writer) {
+ if (folderInfo == null) {
+ return;
+ }
+
+ int startingRank = 0;
+ if (folderAlreadyCreated) {
+ startingRank = folderInfo.contents.size();
+ }
+
+ for (ShortcutInfo info : pendingShortcuts) {
+ info.rank = startingRank++;
+ writer.addItemToDatabase(info, folderInfo.id, 0, 0, 0);
+ }
+
+ if (folderAlreadyCreated) {
+ // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
+ new MainThreadExecutor().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ folderInfo.prepareAutoUpdate();
+ for (ShortcutInfo info : pendingShortcuts) {
+ folderInfo.add(info, false);
+ }
+ }
+ });
+ } else {
+ prefs.edit().putLong(folderIdKey, folderInfo.id).apply();
+ }
+ }
+ }
+
+ /**
+ * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
+ */
+ public static void processAllUsers(List<UserHandle> users, Context context) {
+ UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+ HashSet<String> validKeys = new HashSet<>();
+ for (UserHandle user : users) {
+ validKeys.add(USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user));
+ }
+
+ SharedPreferences prefs = prefs(context);
+ SharedPreferences.Editor editor = prefs.edit();
+ for (String key : prefs.getAll().keySet()) {
+ if (!validKeys.contains(key)) {
+ editor.remove(key);
+ }
+ }
+ editor.apply();
+ }
+
+ /**
+ * For each user, if a work folder has not been created, mark it such that the folder will
+ * never get created.
+ */
+ public static void markExistingUsersForNoFolderCreation(Context context) {
+ UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+ UserHandle myUser = Process.myUserHandle();
+
+ SharedPreferences prefs = null;
+ for (UserHandle user : userManager.getUserProfiles()) {
+ if (myUser.equals(user)) {
+ continue;
+ }
+ if (prefs == null) {
+ prefs = prefs(context);
+ }
+ String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user);
+ if (!prefs.contains(folderIdKey)) {
+ prefs.edit().putLong(folderIdKey, ItemInfo.NO_ID).apply();
+ }
+ }
+ }
+
+ public static SharedPreferences prefs(Context context) {
+ return context.getSharedPreferences(
+ LauncherFiles.MANAGED_USER_PREFERENCES_KEY, Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 0b3b632..81df153 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -17,8 +17,6 @@
package com.android.launcher3.util;
import android.app.AppOpsManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -26,23 +24,13 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
-import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.Log;
-import android.widget.Toast;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
-import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -54,8 +42,6 @@
*/
public class PackageManagerHelper {
- private static final String TAG = "PackageManagerHelper";
-
private final Context mContext;
private final PackageManager mPm;
private final LauncherAppsCompat mLauncherApps;
@@ -183,35 +169,4 @@
throw new RuntimeException(e);
}
}
-
-
- /**
- * Starts the details activity for {@code info}
- */
- public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) {
- if (info instanceof PromiseAppInfo) {
- PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
- mContext.startActivity(promiseAppInfo.getMarketIntent(mContext));
- return;
- }
- ComponentName componentName = null;
- if (info instanceof AppInfo) {
- componentName = ((AppInfo) info).componentName;
- } else if (info instanceof ShortcutInfo) {
- componentName = info.getTargetComponent();
- } else if (info instanceof PendingAddItemInfo) {
- componentName = ((PendingAddItemInfo) info).componentName;
- } else if (info instanceof LauncherAppWidgetInfo) {
- componentName = ((LauncherAppWidgetInfo) info).providerName;
- }
- if (componentName != null) {
- try {
- mLauncherApps.showAppDetailsForProfile(
- componentName, info.user, sourceBounds, opts);
- } catch (SecurityException | ActivityNotFoundException e) {
- Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Unable to launch settings", e);
- }
- }
- }
}
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
index ae5bfd5..a647378 100644
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ b/src/com/android/launcher3/util/VerticalSwipeController.java
@@ -18,10 +18,12 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
import android.util.Log;
import android.view.MotionEvent;
@@ -29,10 +31,13 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.touch.SwipeDetector.Direction;
+import java.util.ArrayList;
/**
* Handles vertical touch gesture on the DragLayer allowing transitioning from
@@ -63,6 +68,8 @@
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
+ protected SpringAnimationHandler[] mSpringHandlers;
+
public VerticalSwipeController(Launcher l, LauncherState baseState) {
this(l, baseState, ALL_APPS, SwipeDetector.VERTICAL);
}
@@ -97,6 +104,29 @@
}
}
+ protected void initSprings() {
+ AllAppsContainerView appsView = mLauncher.getAppsView();
+
+ SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+ if (handler == null) {
+ mSpringHandlers = new SpringAnimationHandler[0];
+ return;
+ }
+
+ ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+ handlers.add(handler);
+
+ SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+ if (searchSpring != null) {
+ SpringAnimationHandler searchHandler =
+ new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+ searchHandler.add(searchSpring, true /* setDefaultValues */);
+ handlers.add(searchHandler);
+ }
+
+ mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+ }
+
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -125,6 +155,10 @@
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
+
+ if (mSpringHandlers == null) {
+ initSprings();
+ }
}
if (mNoIntercept) {
@@ -139,6 +173,9 @@
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.addMovement(ev);
+ }
return mDetector.onTouchEvent(ev);
}
@@ -161,6 +198,10 @@
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
}
+
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.skipToEnd();
+ }
}
protected boolean isTransitionFlipped() {
@@ -203,6 +244,13 @@
}
}
+ if (fling && targetState == mTargetState) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+ h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+ }
+ }
+
mCurrentAnimation.setEndAction(() -> {
mLauncher.getStateManager().goToState(targetState, false);
onTransitionComplete(fling, targetState == mToState);
diff --git a/src/com/android/launcher3/views/AllAppsScrim.java b/src/com/android/launcher3/views/AllAppsScrim.java
new file mode 100644
index 0000000..0ef9c8f
--- /dev/null
+++ b/src/com/android/launcher3/views/AllAppsScrim.java
@@ -0,0 +1,233 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v4.graphics.ColorUtils;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.dynamicui.WallpaperColorInfo.OnChangeListener;
+import com.android.launcher3.graphics.NinePatchDrawHelper;
+import com.android.launcher3.graphics.ShadowGenerator;
+import com.android.launcher3.util.Themes;
+
+import static com.android.launcher3.graphics.NinePatchDrawHelper.EXTENSION_PX;
+
+public class AllAppsScrim extends View implements OnChangeListener, Insettable {
+
+ private static final int MAX_ALPHA = 235;
+ private static final int MIN_ALPHA_PORTRAIT = 100;
+ private static final int MIN_ALPHA_LANDSCAPE = MAX_ALPHA;
+
+ protected final WallpaperColorInfo mWallpaperColorInfo;
+ private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ private final Rect mDrawRect = new Rect();
+ private final Rect mPadding = new Rect();
+ private final Rect mInsets = new Rect();
+
+ private final float mRadius;
+ private final int mScrimColor;
+
+ private int mMinAlpha;
+ private int mAlphaRange;
+
+ private final float mShadowBlur;
+ private final Bitmap mShadowBitmap;
+
+ private final NinePatchDrawHelper mShadowHelper = new NinePatchDrawHelper();
+
+ private int mFillAlpha;
+
+ private float mDrawHeight;
+ private float mDrawOffsetY;
+
+ public AllAppsScrim(Context context) {
+ this(context, null);
+ }
+
+ public AllAppsScrim(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AllAppsScrim(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
+ mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+ mRadius = getResources().getDimension(R.dimen.all_apps_scrim_radius);
+ mShadowBlur = getResources().getDimension(R.dimen.all_apps_scrim_blur);
+
+ initDefaults();
+ mFillAlpha = mMinAlpha;
+ mShadowBitmap = generateShadowBitmap();
+
+ updateColors(mWallpaperColorInfo);
+ }
+
+ private DeviceProfile initDefaults() {
+ DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ mMinAlpha = grid.isVerticalBarLayout()
+ ? MIN_ALPHA_LANDSCAPE : MIN_ALPHA_PORTRAIT;
+ mAlphaRange = MAX_ALPHA - mMinAlpha;
+ return grid;
+ }
+
+ private Bitmap generateShadowBitmap() {
+ float curveBot = mRadius + mShadowBlur;
+
+ ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
+ builder.radius = mRadius;
+ builder.shadowBlur = mShadowBlur;
+
+ // Create the bitmap such that only the top half is drawn in the bitmap.
+ int bitmapWidth = 2 * Math.round(curveBot) + EXTENSION_PX;
+ int bitmapHeight = bitmapWidth / 2;
+ Bitmap result = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+
+ float fullSize = 2 * curveBot + EXTENSION_PX - mShadowBlur;
+ builder.bounds.set(mShadowBlur, mShadowBlur, fullSize, fullSize);
+ builder.drawShadow(new Canvas(result));
+ return result;
+ }
+
+ public Bitmap getShadowBitmap() {
+ return mShadowBitmap;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mWallpaperColorInfo.addOnChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mWallpaperColorInfo.removeOnChangeListener(this);
+ }
+
+ @Override
+ public void onExtractedColorsChanged(WallpaperColorInfo info) {
+ updateColors(info);
+ invalidate();
+ }
+
+ private void updateColors(WallpaperColorInfo info) {
+ mFillPaint.setColor(ColorUtils.compositeColors(mScrimColor,
+ ColorUtils.compositeColors(mScrimColor, info.getMainColor())));
+ mFillPaint.setAlpha(mFillAlpha);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ float edgeTop = getHeight() + mDrawOffsetY - mDrawHeight + mPadding.top;
+ float edgeRight = getWidth() - mPadding.right;
+
+ if (mPadding.left > 0 || mPadding.right > 0) {
+ mShadowHelper.drawVerticallyStretched(mShadowBitmap, canvas,
+ mPadding.left - mShadowBlur,
+ edgeTop - mShadowBlur,
+ edgeRight + mShadowBlur,
+ getHeight());
+ } else {
+ mShadowHelper.draw(mShadowBitmap, canvas, mPadding.left - mShadowBlur,
+ edgeTop - mShadowBlur, edgeRight + mShadowBlur);
+ }
+ canvas.drawRoundRect(mPadding.left, edgeTop, edgeRight,
+ getHeight() + mRadius, mRadius, mRadius, mFillPaint);
+ }
+
+ public void setProgress(float translateY, float alpha) {
+ int newAlpha = Math.round(alpha * mAlphaRange + mMinAlpha);
+ // Negative translation means the scrim is moving up. For negative translation, we change
+ // draw offset as it requires redraw (since more area of the scrim needs to be shown). For
+ // position translation, we simply translate the scrim down as it avoids invalidate and
+ // hence could be optimized by the platform.
+ float drawOffsetY = Math.min(translateY, 0);
+
+ if (newAlpha != mFillAlpha || drawOffsetY != mDrawOffsetY) {
+ invalidateDrawRect();
+
+ mFillAlpha = newAlpha;
+ mFillPaint.setAlpha(mFillAlpha);
+ mDrawOffsetY = drawOffsetY;
+ invalidateDrawRect();
+ }
+
+ setTranslationY(Math.max(translateY, 0));
+ }
+
+ private void invalidateDrawRect() {
+ mDrawRect.top = (int) (getHeight()
+ + mDrawOffsetY - mDrawHeight + mPadding.top - mShadowBlur - 0.5f);
+ invalidate(mDrawRect);
+ }
+
+ public void setDrawRegion(float height) {
+ mDrawHeight = height;
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ DeviceProfile grid = initDefaults();
+ if (grid.isVerticalBarLayout()) {
+ mPadding.set(grid.workspacePadding);
+ mPadding.bottom = 0;
+ mPadding.left += mInsets.left;
+ mPadding.top = mInsets.top;
+ mPadding.right += mInsets.right;
+ setDrawRegion(0);
+ } else {
+ mPadding.setEmpty();
+ float scrimMargin = getResources().getDimension(R.dimen.all_apps_scrim_margin);
+ setDrawRegion(grid.hotseatBarSizePx + insets.bottom + scrimMargin);
+ }
+ updateDrawRect(grid);
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateDrawRect(Launcher.getLauncher(getContext()).getDeviceProfile());
+ }
+
+ private void updateDrawRect(DeviceProfile grid) {
+ mDrawRect.bottom = getHeight();
+ if (grid.isVerticalBarLayout()) {
+ mDrawRect.left = (int) (mPadding.left - mShadowBlur - 0.5f);
+ mDrawRect.right = (int) (getWidth() - mPadding.right + 0.5f);
+ } else {
+ mDrawRect.left = 0;
+ mDrawRect.right = getWidth();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index 01b63be..c8203f7 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -63,10 +63,10 @@
ColorUtils.setAlphaComponent(mShadowInfo.ambientShadowColor, alpha));
drawWithoutBadge(canvas);
- canvas.save();
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
getScrollX() + getWidth(),
- getScrollY() + getHeight());
+ getScrollY() + getHeight(), Region.Op.INTERSECT);
getPaint().setShadowLayer(mShadowInfo.keyShadowBlur, 0.0f, mShadowInfo.keyShadowOffset,
ColorUtils.setAlphaComponent(mShadowInfo.keyShadowColor, alpha));
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 1cd6699..fc121d3 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -22,8 +22,6 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Property;
@@ -45,7 +43,6 @@
public class RecyclerViewFastScroller extends View {
private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
- private static final Rect sTempRect = new Rect();
private static final Property<RecyclerViewFastScroller, Integer> TRACK_WIDTH =
new Property<RecyclerViewFastScroller, Integer>(Integer.class, "width") {
@@ -207,9 +204,9 @@
* Handles the touch event and determines whether to show the fast scroller (or updates it if
* it is already showing).
*/
- public boolean handleTouchEvent(MotionEvent ev, Point offset) {
- int x = (int) ev.getX() - offset.x;
- int y = (int) ev.getY() - offset.y;
+ public boolean handleTouchEvent(MotionEvent ev) {
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// Keep track of the down positions
@@ -263,6 +260,7 @@
}
private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
+ mRv.getParent().requestDisallowInterceptTouchEvent(true);
mIsDragging = true;
if (mCanThumbDetach) {
mIsThumbDetached = true;
@@ -291,7 +289,7 @@
if (mThumbOffsetY < 0) {
return;
}
- int saveCount = canvas.save();
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(getWidth() / 2, mRv.getScrollBarTop());
// Draw the track
float halfW = mWidth / 2;
@@ -360,16 +358,4 @@
mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
mPopupView.setTranslationY(top);
}
-
- public boolean isHitInParent(float x, float y, Point outOffset) {
- if (mThumbOffsetY < 0) {
- return false;
- }
- getHitRect(sTempRect);
- sTempRect.top += mRv.getScrollBarTop();
- if (outOffset != null) {
- outOffset.set(sTempRect.left, sTempRect.top);
- }
- return sTempRect.contains((int) x, (int) y);
- }
}
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
deleted file mode 100644
index 090b3e6..0000000
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.EdgeEffectFactory;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.widget.EdgeEffect;
-import android.widget.RelativeLayout;
-
-import static android.support.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-import static android.support.animation.SpringForce.STIFFNESS_LOW;
-import static android.support.animation.SpringForce.STIFFNESS_MEDIUM;
-
-public class SpringRelativeLayout extends RelativeLayout {
-
- private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
- private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
- private static final float VELOCITY_MULTIPLIER = 0.3f;
-
- private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
- new FloatPropertyCompat<SpringRelativeLayout>("value") {
-
- @Override
- public float getValue(SpringRelativeLayout object) {
- return object.mDampedScrollShift;
- }
-
- @Override
- public void setValue(SpringRelativeLayout object, float value) {
- object.setDampedScrollShift(value);
- }
- };
-
- private final SparseBooleanArray mSpringViews = new SparseBooleanArray();
- private final SpringAnimation mSpring;
-
- private float mDampedScrollShift = 0;
-
- public SpringRelativeLayout(Context context) {
- this(context, null);
- }
-
- public SpringRelativeLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
- mSpring.setSpring(new SpringForce(0)
- .setStiffness(STIFFNESS)
- .setDampingRatio(DAMPING_RATIO));
- }
-
- public void addSpringView(int id) {
- mSpringViews.put(id, true);
- }
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
- canvas.translate(0, mDampedScrollShift);
- boolean result = super.drawChild(canvas, child, drawingTime);
- canvas.translate(0, -mDampedScrollShift);
- return result;
- }
- return super.drawChild(canvas, child, drawingTime);
- }
-
- private void setDampedScrollShift(float shift) {
- if (shift != mDampedScrollShift) {
- mDampedScrollShift = shift;
- invalidate();
- }
- }
-
- private void finishScrollWithVelocity(float velocity) {
- mSpring.setStartVelocity(velocity);
- mSpring.setStartValue(mDampedScrollShift);
- mSpring.start();
- }
-
- public EdgeEffectFactory createEdgeEffectFactory() {
- return new SpringEdgeEffectFactory();
- }
-
- private class SpringEdgeEffectFactory extends EdgeEffectFactory {
-
- @NonNull @Override
- protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
- switch (direction) {
- case DIRECTION_TOP:
- return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
- case DIRECTION_BOTTOM:
- return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
- }
- return super.createEdgeEffect(view, direction);
- }
- }
-
- private class SpringEdgeEffect extends EdgeEffect {
-
- private final float mVelocityMultiplier;
-
- private float mDistance;
-
- public SpringEdgeEffect(Context context, float velocityMultiplier) {
- super(context);
- mVelocityMultiplier = velocityMultiplier;
- }
-
- @Override
- public boolean draw(Canvas canvas) {
- return false;
- }
-
- @Override
- public void onAbsorb(int velocity) {
- finishScrollWithVelocity(velocity * mVelocityMultiplier);
- }
-
- @Override
- public void onPull(float deltaDistance, float displacement) {
- mDistance += deltaDistance * (mVelocityMultiplier / 3f);
- setDampedScrollShift(mDistance * getHeight());
- }
-
- @Override
- public void onRelease() {
- mDistance = 0;
- finishScrollWithVelocity(0);
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
index 7888b08..ba223c4 100644
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ b/src/com/android/launcher3/views/TopRoundedCornerView.java
@@ -17,59 +17,39 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
/**
* View with top rounded corners.
*/
-public class TopRoundedCornerView extends SpringRelativeLayout {
+public class TopRoundedCornerView extends FrameLayout {
private final RectF mRect = new RectF();
private final Path mClipPath = new Path();
private float[] mRadii;
- private final Paint mNavBarScrimPaint;
- private int mNavBarScrimHeight = 0;
-
public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int radius = getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
-
- mNavBarScrimPaint = new Paint();
- mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
}
public TopRoundedCornerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public void setNavBarScrimHeight(int height) {
- if (mNavBarScrimHeight != height) {
- mNavBarScrimHeight = height;
- invalidate();
- }
- }
-
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.clipPath(mClipPath);
super.draw(canvas);
canvas.restore();
-
- if (mNavBarScrimHeight > 0) {
- canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
- mNavBarScrimPaint);
- }
}
@Override
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 10708d6..b22509c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -31,8 +31,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.graphics.ColorScrim;
-import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.graphics.GradientView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.SystemUiController;
@@ -49,11 +48,10 @@
/* Touch handling related member variables. */
private Toast mWidgetInstructionToast;
- protected final ColorScrim mColorScrim;
+ protected GradientView mGradientView;
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mColorScrim = ColorScrim.createExtractedColorScrim(this);
}
@Override
@@ -72,7 +70,7 @@
@Override
public final boolean onLongClick(View v) {
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ if (!mLauncher.isDraggingEnabled()) return false;
if (v instanceof WidgetCell) {
return beginDraggingWidget((WidgetCell) v);
@@ -82,7 +80,7 @@
protected void setTranslationShift(float translationShift) {
super.setTranslationShift(translationShift);
- mColorScrim.setProgress(1 - mTranslationShift);
+ mGradientView.setAlpha(1 - mTranslationShift);
}
private boolean beginDraggingWidget(WidgetCell v) {
diff --git a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
index 3a24c3d..37e5efc 100644
--- a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
@@ -44,8 +44,7 @@
mPaint = new TextPaint();
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
- mLauncher.getDeviceProfile().getFullScreenProfile().iconTextSizePx,
- getResources().getDisplayMetrics()));
+ mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
setBackgroundResource(R.drawable.bg_deferred_app_widget);
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 9d74218..0b1474a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -488,14 +488,13 @@
// Only reinflate when the final configuration is same as the required configuration
if (mReinflateOnConfigChange && isSameOrientation()) {
mReinflateOnConfigChange = false;
- reInflate();
+ if (isAttachedToWindow()) {
+ reInflate();
+ }
}
}
public void reInflate() {
- if (!isAttachedToWindow()) {
- return;
- }
LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
// Remove and rebind the current widget (which was inflated in the wrong
// orientation), but don't delete it from the database
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 961799d..6970833 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -43,7 +43,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -84,7 +83,7 @@
setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
updateAppWidget(null);
- setOnClickListener(ItemClickHandler.INSTANCE);
+ setOnClickListener(mLauncher);
if (info.pendingItemInfo == null) {
info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName());
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index a258485..6a9013d 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -32,6 +32,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.graphics.GradientView;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.PackageUserKey;
@@ -54,6 +55,10 @@
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
mInsets = new Rect();
+
+ mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
+ R.layout.widgets_bottom_sheet_scrim, mLauncher.getDragLayer(), false);
+ mGradientView.setProgress(1, false);
mContent = this;
}
@@ -70,6 +75,7 @@
onWidgetsBound();
+ mLauncher.getDragLayer().addView(mGradientView);
mLauncher.getDragLayer().addView(this);
mIsOpen = false;
open(true);
@@ -151,6 +157,12 @@
}
@Override
+ protected void onCloseComplete() {
+ super.onCloseComplete();
+ mLauncher.getDragLayer().removeView(mGradientView);
+ }
+
+ @Override
protected boolean isOfType(@FloatingViewType int type) {
return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
}
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index a622624..e461afc 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -23,6 +23,7 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.view.animation.AnimationUtils;
import com.android.launcher3.Insettable;
@@ -30,8 +31,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.R;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.TopRoundedCornerView;
/**
* Popup for showing the full list of available widgets
@@ -47,6 +46,7 @@
private final WidgetsListAdapter mAdapter;
+ private View mNavBarScrim;
private WidgetsRecyclerView mRecyclerView;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -65,14 +65,15 @@
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.container);
+ mNavBarScrim = findViewById(R.id.nav_bar_bg);
mRecyclerView = findViewById(R.id.widgets_list_view);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
- TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
- springLayout.addSpringView(R.id.widgets_list_view);
- mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
+ mGradientView = findViewById(R.id.gradient_bg);
+ mGradientView.setProgress(1, false);
+
onWidgetsBound();
}
@@ -93,6 +94,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
+ mNavBarScrim.getLayoutParams().height = insets.bottom;
mRecyclerView.setPadding(
mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
mRecyclerView.getPaddingRight(), insets.bottom);
@@ -101,8 +103,6 @@
} else {
clearNavBarColor();
}
-
- ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
requestLayout();
}
@@ -110,8 +110,12 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthUsed;
if (mInsets.bottom > 0) {
+ // If we have bottom insets, we do not show the scrim as it would overlap
+ // with the navbar scrim
+ mGradientView.setVisibility(View.INVISIBLE);
widthUsed = 0;
} else {
+ mGradientView.setVisibility(View.VISIBLE);
Rect padding = mLauncher.getDeviceProfile().workspacePadding;
widthUsed = Math.max(padding.left + padding.right,
2 * (mInsets.left + mInsets.right));
@@ -120,14 +124,15 @@
int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
- MeasureSpec.getSize(heightMeasureSpec));
+ measureChild(mGradientView, widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(mGradientView.getMeasuredWidth(), mGradientView.getMeasuredHeight());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
+ mGradientView.layout(0, 0, width, height);
// Content is laid out as center bottom aligned
int contentWidth = mContent.getMeasuredWidth();
@@ -172,10 +177,13 @@
mOpenCloseAnimator.removeListener(this);
}
});
- post(() -> {
- mRecyclerView.setLayoutFrozen(true);
- mOpenCloseAnimator.start();
- mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.setLayoutFrozen(true);
+ mOpenCloseAnimator.start();
+ mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
+ }
});
} else {
setTranslationShift(TRANSLATION_SHIFT_OPENED);
@@ -198,11 +206,7 @@
// Disable swipe down when recycler view is scrolling
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
- RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
- if (scroller.getThumbOffsetY() >= 0 &&
- mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
- mNoIntercept = true;
- } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+ if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
}
}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 124058e..89c88a4 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,12 +17,8 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Point;
import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
@@ -31,15 +27,13 @@
/**
* The widgets recycler view.
*/
-public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouchListener {
+public class WidgetsRecyclerView extends BaseRecyclerView {
+ private static final String TAG = "WidgetsRecyclerView";
private WidgetsListAdapter mAdapter;
private final int mScrollbarTop;
- private final Point mFastScrollerOffset = new Point();
- private boolean mTouchDownOnScroller;
-
public WidgetsRecyclerView(Context context) {
this(context, null);
}
@@ -52,7 +46,6 @@
// API 21 and below only support 3 parameter ctor.
super(context, attrs, defStyleAttr);
mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
- addOnItemTouchListener(this);
}
public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
@@ -63,6 +56,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ addOnItemTouchListener(this);
// create a layout manager with Launcher's context so that scroll position
// can be preserved during screen rotation.
setLayoutManager(new LinearLayoutManager(getContext()));
@@ -151,26 +145,4 @@
public int getScrollBarTop() {
return mScrollbarTop;
}
-
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- if (e.getAction() == MotionEvent.ACTION_DOWN) {
- mTouchDownOnScroller =
- mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
- }
- if (mTouchDownOnScroller) {
- return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
- }
- return false;
- }
-
- @Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (mTouchDownOnScroller) {
- mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
- }
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
index 2acd29b..fd33ee1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -33,8 +33,7 @@
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
- | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/FastOverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/FastOverviewState.java
deleted file mode 100644
index 147d194..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/FastOverviewState.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-/**
- * A dummy overview state
- */
-public class FastOverviewState extends OverviewState {
-
- public FastOverviewState(int id) {
- super(id);
- }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
index d9ce87c..88a1e10 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
@@ -27,13 +27,13 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.views.OptionsPopupView;
/**
* Accessibility delegate with actions pointing to various Overview entry points.
*/
public class OverviewAccessibilityDelegate extends AccessibilityDelegate {
+ private static final int OVERVIEW = R.string.accessibility_action_overview;
private static final int WALLPAPERS = R.string.wallpaper_button_text;
private static final int WIDGETS = R.string.widget_button_text;
private static final int SETTINGS = R.string.settings_button_text;
@@ -43,6 +43,7 @@
super.onInitializeAccessibilityNodeInfo(host, info);
Context context = host.getContext();
+ info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
if (Utilities.isWallpaperAllowed(context)) {
info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
@@ -54,13 +55,18 @@
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
Launcher launcher = Launcher.getLauncher(host.getContext());
- if (action == WALLPAPERS) {
+ OverviewPanel overviewPanel = launcher.findViewById(R.id.overview_panel);
+ if (action == OVERVIEW) {
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW);
+ return true;
+ } else if (action == WALLPAPERS) {
launcher.onClickWallpaperPicker(host);
return true;
} else if (action == WIDGETS) {
- return OptionsPopupView.onWidgetsClicked(launcher);
+ overviewPanel.onClickAddWidgetButton();
+ return true;
} else if (action == SETTINGS) {
- OptionsPopupView.startSettings(launcher);
+ overviewPanel.onClickSettingsButton(host);
return true;
}
return super.performAccessibilityAction(host, action, args);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
new file mode 100644
index 0000000..4168e11
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -0,0 +1,192 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+public class OverviewPanel extends LinearLayout implements Insettable, View.OnClickListener,
+ View.OnLongClickListener, LauncherStateManager.StateHandler {
+
+ // Out of 100, the percent of space the overview bar should try and take vertically.
+ private static final float OVERVIEW_ICON_ZONE_RATIO = 0.22f;
+
+ private final Launcher mLauncher;
+
+ public OverviewPanel(Context context) {
+ this(context, null);
+ }
+
+ public OverviewPanel(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OverviewPanel(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ setAlpha(0);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ int visibleChildCount = 3;
+ // Attach buttons.
+ attachListeners(findViewById(R.id.wallpaper_button));
+ attachListeners(findViewById(R.id.widget_button));
+
+ View settingsButton = findViewById(R.id.settings_button);
+ if (mLauncher.hasSettings()) {
+ attachListeners(settingsButton);
+ } else {
+ settingsButton.setVisibility(GONE);
+ visibleChildCount--;
+ }
+
+ // Init UI
+ Resources res = getResources();
+ int itemWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
+ int spacerWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
+
+ int totalItemWidth = visibleChildCount * itemWidthPx;
+ int maxWidth = totalItemWidth + (visibleChildCount - 1) * spacerWidthPx;
+
+ getLayoutParams().width = Math.min(mLauncher.getDeviceProfile().availableWidthPx, maxWidth);
+ getLayoutParams().height = getButtonBarHeight(mLauncher);
+ }
+
+ private void attachListeners(View view) {
+ view.setOnClickListener(this);
+ view.setOnLongClickListener(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin = insets.bottom;
+ }
+
+ @Override
+ public void onClick(View view) {
+ handleViewClick(view, Action.Touch.TAP);
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ return handleViewClick(view, Action.Touch.LONGPRESS);
+ }
+
+ private boolean handleViewClick(View view, int action) {
+ if (mLauncher.getWorkspace().isSwitchingState()) {
+ return false;
+ }
+
+ final int controlType;
+ if (view.getId() == R.id.wallpaper_button) {
+ mLauncher.onClickWallpaperPicker(view);
+ controlType = ControlType.WALLPAPER_BUTTON;
+ } else if (view.getId() == R.id.widget_button) {
+ onClickAddWidgetButton();
+ controlType = ControlType.WIDGETS_BUTTON;
+ } else if (view.getId() == R.id.settings_button) {
+ onClickSettingsButton(view);
+ controlType = ControlType.SETTINGS_BUTTON;
+ } else {
+ return false;
+ }
+
+ mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
+ return true;
+ }
+
+ /**
+ * Event handler for the (Add) Widgets button that appears after a long press
+ * on the home screen.
+ */
+ public void onClickAddWidgetButton() {
+ if (getContext().getPackageManager().isSafeMode()) {
+ Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ } else {
+ WidgetsFullSheet.show(mLauncher, true /* animated */);
+ }
+ }
+
+ /**
+ * Event handler for a click on the settings button that appears after a long press
+ * on the home screen.
+ */
+ public void onClickSettingsButton(View v) {
+ Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(getContext().getPackageName());
+ intent.setSourceBounds(mLauncher.getViewBounds(v));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent, mLauncher.getActivityLaunchOptionsAsBundle(v, false));
+ }
+
+ @Override
+ public void setState(LauncherState state) {
+ setState(state, NO_ANIM_PROPERTY_SETTER);
+ }
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState,
+ AnimatorSetBuilder builder, AnimationConfig config) {
+ setState(toState, new AnimatedPropertySetter(config.duration, builder));
+ }
+
+ private void setState(LauncherState state, PropertySetter setter) {
+ setter.setViewAlpha(this, 1f - state.getHoseatAlpha(mLauncher), Interpolators.ACCEL);
+ }
+
+ public static int getButtonBarHeight(Launcher launcher) {
+ int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO *
+ launcher.getDeviceProfile().availableHeightPx);
+ Resources res = launcher.getResources();
+ int overviewModeMinIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+ int overviewModeMaxIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+ return Utilities.boundToRange(zoneHeight,
+ overviewModeMinIconZoneHeightPx,
+ overviewModeMaxIconZoneHeightPx);
+ }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index 3dfbc40..d18901d 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -33,7 +33,56 @@
*/
public class OverviewState extends LauncherState {
+ // The percent to shrink the workspace during overview mode
+ private static final float SCALE_FACTOR = 0.7f;
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
+ FLAG_DISABLE_PAGE_CLIPPING;
+
public OverviewState(int id) {
- super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, FLAG_DISABLE_RESTORE);
+ super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ }
+
+ @Override
+ public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+ DeviceProfile grid = launcher.getDeviceProfile();
+ Workspace ws = launcher.getWorkspace();
+ Rect insets = launcher.getDragLayer().getInsets();
+
+ int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
+ int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
+ int workspaceTop = insets.top + grid.workspacePadding.top;
+ int workspaceBottom = ws.getHeight() - insets.bottom - grid.workspacePadding.bottom;
+ int overviewTop = insets.top;
+ int overviewBottom = ws.getHeight() - insets.bottom - overviewButtonBarHeight;
+ int workspaceOffsetTopEdge =
+ workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+ int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+ return new float[] {SCALE_FACTOR, 0, -workspaceOffsetTopEdge + overviewOffsetTopEdge };
+ }
+
+ @Override
+ public float getHoseatAlpha(Launcher launcher) {
+ return 0;
+ }
+
+ @Override
+ public void onStateEnabled(Launcher launcher) {
+ launcher.getWorkspace().setPageRearrangeEnabled(true);
+
+ if (isAccessibilityEnabled(launcher)) {
+ launcher.getOverviewPanel().getChildAt(0).performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+ }
+
+ @Override
+ public void onStateDisabled(Launcher launcher) {
+ launcher.getWorkspace().setPageRearrangeEnabled(false);
+ }
+
+ @Override
+ public View getFinalFocus(Launcher launcher) {
+ return launcher.getOverviewPanel();
}
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
new file mode 100644
index 0000000..a7c8cee
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
@@ -0,0 +1,164 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Detects pinches and animates the Workspace to/from overview mode.
+ */
+public class PinchToOverviewListener extends AnimatorListenerAdapter
+ implements TouchController, OnScaleGestureListener {
+
+ private static final float ACCEPT_THRESHOLD = 0.65f;
+ /**
+ * The velocity threshold at which a pinch will be completed instead of canceled,
+ * even if the first threshold has not been passed. Measured in scale / millisecond
+ */
+ private static final float FLING_VELOCITY = 0.001f;
+
+ private final ScaleGestureDetector mPinchDetector;
+ private Launcher mLauncher;
+ private Workspace mWorkspace = null;
+ private boolean mPinchStarted = false;
+
+ private AnimatorPlaybackController mCurrentAnimation;
+ private float mCurrentScale;
+ private boolean mShouldGoToFinalState;
+
+ private LauncherState mToState;
+
+ public PinchToOverviewListener(Launcher launcher) {
+ mLauncher = launcher;
+ mPinchDetector = new ScaleGestureDetector(mLauncher, this);
+ }
+
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ mPinchDetector.onTouchEvent(ev);
+ return mPinchStarted;
+ }
+
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mPinchDetector.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (isAccessibilityEnabled(mLauncher)) {
+ return false;
+ }
+ if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
+ // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+ return false;
+ }
+ if (mCurrentAnimation != null) {
+ // Don't listen for the pinch gesture if we are already animating from a previous one.
+ return false;
+ }
+ if (mLauncher.isWorkspaceLocked()) {
+ // Don't listen for the pinch gesture if the workspace isn't ready.
+ return false;
+ }
+ if (mWorkspace == null) {
+ mWorkspace = mLauncher.getWorkspace();
+ }
+ if (mWorkspace.isSwitchingState()) {
+ // Don't listen for the pinch gesture while switching state, as it will cause a jump
+ // once the state switching animation is complete.
+ return false;
+ }
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ // Don't listen for the pinch gesture if a floating view is open.
+ return false;
+ }
+
+ if (mLauncher.getDragController().isDragging()) {
+ mLauncher.getDragController().cancelDrag();
+ }
+
+ mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
+ mCurrentAnimation.getTarget().addListener(this);
+ mPinchStarted = true;
+ mCurrentScale = 1;
+ mShouldGoToFinalState = false;
+
+ mCurrentAnimation.dispatchOnStart();
+ return true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentAnimation = null;
+ mPinchStarted = false;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mShouldGoToFinalState) {
+ mCurrentAnimation.start();
+ } else {
+ mCurrentAnimation.setEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.getStateManager().goToState(
+ mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
+ }
+ });
+ mCurrentAnimation.reverse();
+ }
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mCurrentScale = detector.getScaleFactor() * mCurrentScale;
+
+ // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
+ // 0 => Animation complete
+ // 1=> Animation started
+ float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
+
+ float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
+ if (Math.abs(velocity) >= FLING_VELOCITY) {
+ LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
+ mShouldGoToFinalState = toState == mToState;
+ } else {
+ mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
+ }
+
+ // Move the transition animation to that duration.
+ mCurrentAnimation.setPlayFraction(1 - animationFraction);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index a9694a7..64a29ea 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,16 +16,28 @@
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.view.View;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.util.TouchController;
public class UiFactory {
+ public static final boolean USE_HARDWARE_BITMAP = false;
+
public static TouchController[] createTouchControllers(Launcher launcher) {
- return new TouchController[] {new AllAppsSwipeController(launcher)};
+ return new TouchController[] {
+ new AllAppsSwipeController(launcher), new PinchToOverviewListener(launcher)};
}
public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
@@ -34,14 +46,22 @@
public static StateHandler[] getStateHandler(Launcher launcher) {
return new StateHandler[] {
+ (OverviewPanel) launcher.getOverviewPanel(),
launcher.getAllAppsController(), launcher.getWorkspace() };
}
+ public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
+
+ public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
+ BitmapRenderer renderer) {
+ Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ renderer.render(new Canvas(result));
+ return result;
+ }
+
public static void resetOverview(Launcher launcher) { }
public static void onLauncherStateOrFocusChanged(Launcher launcher) { }
-
- public static void onStart(Launcher launcher) { }
-
- public static void onTrimMemory(Launcher launcher, int level) { }
}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index a54268a..0a29147 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -33,17 +33,6 @@
android:resource="@xml/appwidget_no_config" />
</receiver>
-
- <receiver
- android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
- android:label="Hidden widget">
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/appwidget_hidden" />
- </receiver>
-
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
android:label="With Config">
diff --git a/tests/res/xml/appwidget_hidden.xml b/tests/res/xml/appwidget_hidden.xml
deleted file mode 100644
index 6f0e006..0000000
--- a/tests/res/xml/appwidget_hidden.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<appwidget-provider
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="180dp"
- android:minHeight="110dp"
- android:updatePeriodMillis="86400000"
- android:initialLayout="@layout/test_layout_appwidget_blue"
- android:resizeMode="horizontal|vertical"
- android:widgetFeatures="hide_from_picker"
- android:widgetCategory="home_screen">
-</appwidget-provider>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_with_config.xml b/tests/res/xml/appwidget_with_config.xml
index 8403689..3e96c6f 100644
--- a/tests/res/xml/appwidget_with_config.xml
+++ b/tests/res/xml/appwidget_with_config.xml
@@ -7,6 +7,5 @@
android:initialLayout="@layout/test_layout_appwidget_blue"
android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"
android:resizeMode="horizontal|vertical"
- android:widgetFeatures="reconfigurable"
android:widgetCategory="home_screen">
</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index b217847..cf90afd 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -1,11 +1,5 @@
package com.android.launcher3.model;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,8 +24,8 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.graphics.BitmapInfo;
import com.android.launcher3.util.ComponentKey;
@@ -49,6 +43,12 @@
import java.util.List;
import java.util.concurrent.Executor;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
/**
* Base class for writing tests for Model update tasks.
*/
@@ -82,7 +82,7 @@
modelWriter = mock(ModelWriter.class);
when(appState.getModel()).thenReturn(model);
- when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter);
+ when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
when(model.getCallback()).thenReturn(callbacks);
myUser = Process.myUserHandle();
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java
deleted file mode 100644
index 83492bf..0000000
--- a/tests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.android.launcher3.testcomponent;
-
-import android.appwidget.AppWidgetProvider;
-
-/**
- * A simple app widget without any configuration screen and is hidden in picker.
- */
-public class AppWidgetHidden extends AppWidgetProvider { }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 011aa22..1be33d2 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -40,11 +40,13 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.util.ManagedProfileHeuristic;
import org.junit.Before;
@@ -65,7 +67,6 @@
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
public static final long DEFAULT_UI_TIMEOUT = 3000;
- public static final long LARGE_UI_TIMEOUT = 10000;
public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -81,6 +82,11 @@
}
protected void lockRotation(boolean naturalOrientation) throws RemoteException {
+ Utilities.getPrefs(mTargetContext)
+ .edit()
+ .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation)
+ .commit();
+
if (naturalOrientation) {
mDevice.setOrientationNatural();
} else {
@@ -98,13 +104,8 @@
protected UiObject2 openAllApps() {
mDevice.waitForIdle();
if (FeatureFlags.NO_ALL_APPS_ICON) {
- UiObject2 hotseat = mDevice.wait(
- Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
- Point start = hotseat.getVisibleCenter();
- int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
- // 100 px/step
- mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
-
+ // clicking on the page indicator brings up all apps tray on non tablets.
+ findViewById(R.id.page_indicator).click();
} else {
mDevice.wait(Until.findObject(
By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
@@ -220,6 +221,7 @@
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
+ ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
LauncherAppState.getInstance(mTargetContext).getModel().forceReload();
}
});
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
deleted file mode 100644
index ccee7da..0000000
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.ui;
-
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
-import com.android.launcher3.util.rule.ShellCommandRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertTrue;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class WorkTabTest extends AbstractLauncherUiTest {
- @Rule
- public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
- @Rule
- public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
-
- private int mProfileUserId;
-
- @Before
- public void createWorkProfile() throws Exception {
- String output =
- mDevice.executeShellCommand(
- "pm create-user --profileOf 0 --managed TestProfile");
- assertTrue("Failed to create work profile", output.startsWith("Success"));
-
- String[] tokens = output.split("\\s+");
- mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
-
- mDevice.executeShellCommand("am start-user " + mProfileUserId);
- }
-
- @After
- public void removeWorkProfile() throws Exception {
- mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
- }
-
- @Test
- public void workTabExists() {
- mActivityMonitor.startLauncher();
-
- // Open all apps and wait for load complete
- final UiObject2 appsContainer = openAllApps();
- assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
-
- assertTrue("Personal tab is missing",
- mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_personal)),
- LARGE_UI_TIMEOUT));
- assertTrue("Work tab is missing",
- mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_work)), LARGE_UI_TIMEOUT));
- }
-}
\ No newline at end of file