Merge "Import translations. DO NOT MERGE" into ub-launcher3-master
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index ef50ac4..9069698 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index a8b91c5..22f014a 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -19,7 +19,6 @@
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="center"
android:clipChildren="false"
android:clipToPadding="false"
android:alpha="0.0"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index fdf1adc..839d934 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -23,14 +23,14 @@
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="24dp"
+ android:layout_marginTop="@dimen/task_thumbnail_top_margin"
android:scaleType="matrix"
android:background="@drawable/task_thumbnail_background"
android:elevation="4dp" />
<ImageView
android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal"
android:elevation="5dp"/>
</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..4f85957
--- /dev/null
+++ b/quickstep/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+ <dimen name="task_thumbnail_top_margin">24dp</dimen>
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+
+ <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
+ <dimen name="quickstep_fling_min_velocity">250dp</dimen>
+
+</resources>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 26f5d5b..1176034 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -29,9 +29,6 @@
*/
public class OverviewState extends LauncherState {
- // The percent to shrink the workspace during overview mode
- public static final float SCALE_FACTOR = 0.7f;
-
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
public OverviewState(int id) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index c490c3f..20abdc7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,12 +16,17 @@
package com.android.launcher3.uioverrides;
+import android.content.Intent;
import android.view.View.AccessibilityDelegate;
+import android.widget.PopupMenu;
+import android.widget.Toast;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.R;
import com.android.launcher3.VerticalSwipeController;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsFullSheet;
public class UiFactory {
@@ -38,4 +43,29 @@
launcher.getAllAppsController(), launcher.getWorkspace(),
new RecentsViewStateController(launcher)};
}
+
+ public static void onWorkspaceLongPress(Launcher launcher) {
+ PopupMenu menu = new PopupMenu(launcher, launcher.getWorkspace().getPageIndicator());
+ menu.getMenu().add(R.string.wallpaper_button_text).setOnMenuItemClickListener((i) -> {
+ launcher.onClickWallpaperPicker(null);
+ return true;
+ });
+ menu.getMenu().add(R.string.widget_button_text).setOnMenuItemClickListener((i) -> {
+ if (launcher.getPackageManager().isSafeMode()) {
+ Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+ } else {
+ WidgetsFullSheet.show(launcher, true /* animated */);
+ }
+ return true;
+ });
+ if (launcher.hasSettings()) {
+ menu.getMenu().add(R.string.settings_button_text).setOnMenuItemClickListener((i) -> {
+ launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(launcher.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ return true;
+ });
+ }
+ menu.show();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
new file mode 100644
index 0000000..1f6781e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -0,0 +1,79 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
+
+/**
+ * A mutable float which allows animating the value
+ */
+public class AnimatedFloat {
+
+ public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
+ @Override
+ public void setValue(AnimatedFloat obj, float v) {
+ obj.updateValue(v);
+ }
+
+ @Override
+ public Float get(AnimatedFloat obj) {
+ return obj.value;
+ }
+ };
+
+ private final Runnable mUpdateCallback;
+ private ObjectAnimator mValueAnimator;
+
+ public float value;
+
+ public AnimatedFloat(Runnable updateCallback) {
+ mUpdateCallback = updateCallback;
+ }
+
+ public ObjectAnimator animateToValue(float v) {
+ if (mValueAnimator != null) {
+ mValueAnimator.cancel();
+ }
+ mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, v);
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mValueAnimator == animator) {
+ mValueAnimator = null;
+ }
+ }
+ });
+ return mValueAnimator;
+ }
+
+ /**
+ * Changes the value and calls the callback.
+ * Note that the value can be directly accessed as well to avoid notifying the callback.
+ */
+ public void updateValue(float v) {
+ if (Float.compare(v, value) != 0) {
+ value = v;
+ mUpdateCallback.run();
+ }
+ }
+
+ public ObjectAnimator getCurrentAnimation() {
+ return mValueAnimator;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
new file mode 100644
index 0000000..e3c3a1b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_MOVE;
+
+import android.view.Choreographer;
+import android.view.MotionEvent;
+
+import com.android.systemui.shared.system.ChoreographerCompat;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Helper class for batching input events
+ */
+public class MotionEventQueue implements Runnable {
+
+ // We use two arrays and swap the current index when one array is being consumed
+ private final EventArray[] mArrays = new EventArray[] {new EventArray(), new EventArray()};
+ private int mCurrentIndex = 0;
+
+ private final Choreographer mChoreographer;
+ private final Consumer<MotionEvent> mConsumer;
+
+ public MotionEventQueue(Choreographer choreographer, Consumer<MotionEvent> consumer) {
+ mChoreographer = choreographer;
+ mConsumer = consumer;
+ }
+
+ public void queue(MotionEvent event) {
+ synchronized (mArrays) {
+ EventArray array = mArrays[mCurrentIndex];
+ if (array.isEmpty()) {
+ ChoreographerCompat.postInputFrame(mChoreographer, this);
+ }
+
+ int eventAction = event.getAction();
+ if (eventAction == ACTION_MOVE && array.lastEventAction == ACTION_MOVE) {
+ // Replace and recycle the last event
+ array.set(array.size() - 1, event).recycle();
+ } else {
+ array.add(event);
+ array.lastEventAction = eventAction;
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ EventArray array = swapAndGetCurrentArray();
+ int size = array.size();
+ for (int i = 0; i < size; i++) {
+ MotionEvent event = array.get(i);
+ mConsumer.accept(event);
+ event.recycle();
+ }
+ array.clear();
+ array.lastEventAction = ACTION_CANCEL;
+ }
+
+ private EventArray swapAndGetCurrentArray() {
+ synchronized (mArrays) {
+ EventArray current = mArrays[mCurrentIndex];
+ mCurrentIndex = mCurrentIndex ^ 1;
+ return current;
+ }
+ }
+
+ private static class EventArray extends ArrayList<MotionEvent> {
+
+ public int lastEventAction = ACTION_CANCEL;
+
+ public EventArray() {
+ super(4);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..cca2729
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util.SparseArray;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+ private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
+
+ private int mState = 0;
+
+ /**
+ * Adds the provided state flags to the global state and executes any callbacks as a result.
+ * @param stateFlag
+ */
+ public void setState(int stateFlag) {
+ mState = mState | stateFlag;
+
+ int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ int state = mCallbacks.keyAt(i);
+
+ if ((mState & state) == state) {
+ Runnable callback = mCallbacks.valueAt(i);
+ if (callback != null) {
+ // Set the callback to null, so that it does not run again.
+ mCallbacks.setValueAt(i, null);
+ callback.run();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the callbacks to be run when the provided states are enabled.
+ * The callback is only run once.
+ */
+ public void addCallback(int stateMask, Runnable callback) {
+ mCallbacks.put(stateMask, callback);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 1a8ae2a..af82fe9 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -20,56 +20,52 @@
import android.animation.RectEvaluator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Handler;
import android.os.UserHandle;
-import android.support.annotation.BinderThread;
import android.support.annotation.UiThread;
-import android.util.FloatProperty;
-import android.view.Choreographer;
-import android.view.Choreographer.FrameCallback;
import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
@TargetApi(Build.VERSION_CODES.O)
-public class NavBarSwipeInteractionHandler extends InternalStateHandler implements FrameCallback {
+public class NavBarSwipeInteractionHandler extends InternalStateHandler {
- private static FloatProperty<NavBarSwipeInteractionHandler> SHIFT =
- new FloatProperty<NavBarSwipeInteractionHandler>("currentShift") {
- @Override
- public void setValue(NavBarSwipeInteractionHandler handler, float v) {
- handler.setShift(v);
- }
+ private static final int STATE_LAUNCHER_READY = 1 << 0;
+ private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
+ private static final int STATE_LOAD_PLAN_READY = 1 << 2;
+ private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
+ private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
+ private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
+ private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
- @Override
- public Float get(NavBarSwipeInteractionHandler handler) {
- return handler.mCurrentShift;
- }
- };
+ private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
+ private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
+ private static final long DEFAULT_SWIPE_DURATION = 200;
- // The following constants need to be scaled based on density. The scaled versions will be
- // assigned to the corresponding member variables below.
- private static final int FLING_THRESHOLD_VELOCITY = 500;
- private static final int MIN_FLING_VELOCITY = 250;
+ // Ideal velocity for a smooth transition
+ private static final float PIXEL_PER_MS = 2f;
private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
@@ -79,47 +75,102 @@
private final Rect mCurrentRect = new Rect();
private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
- private final Bitmap mTaskSnapshot;
- private final int mRunningTaskId;
- private Future<RecentsTaskLoadPlan> mFutureLoadPlan;
-
- private Launcher mLauncher;
- private Choreographer mChoreographer;
- private SnapshotDragView mDragView;
- private RecentsView mRecentsView;
- private Hotseat mHotseat;
-
- private float mStartDelta;
- private float mLastDelta;
-
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
- private float mCurrentShift;
+ private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
- // These are updated on the binder thread, and eventually picked up on doFrame
- private volatile float mCurrentDisplacement;
- private volatile float mEndVelocity;
- private volatile boolean mTouchEnded = false;
+ // Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
+ // animated to 1, so allow for a smooth transition.
+ private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
- NavBarSwipeInteractionHandler(Bitmap taskSnapShot, RunningTaskInfo runningTaskInfo) {
- mTaskSnapshot = taskSnapShot;
+ private final int mRunningTaskId;
+ private final Context mContext;
+
+ private final MultiStateCallback mStateCallback;
+
+ private Launcher mLauncher;
+ private SnapshotDragView mDragView;
+ private RecentsView mRecentsView;
+ private Hotseat mHotseat;
+ private RecentsTaskLoadPlan mLoadPlan;
+
+ private boolean mLauncherReady;
+ private boolean mTouchEndHandled;
+ private float mCurrentDisplacement;
+
+ private Bitmap mTaskSnapshot;
+
+ NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context) {
mRunningTaskId = runningTaskInfo.id;
+ mContext = context;
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+ // Build the state callback
+ mStateCallback = new MultiStateCallback();
+ mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
+ mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
+ this::setTaskPlanToUi);
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
+ mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+ this::onAnimationToLauncherComplete);
+ mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
+ this::cleanupLauncher);
+ }
+
+ private void onLauncherReady() {
+ mLauncherReady = true;
+ executeFrameUpdate();
+
+ // Wait for some time before loading recents so that the first frame is fast
+ new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
+ RECENTS_VIEW_VISIBILITY_DELAY);
+
+ long duration = Math.min(DEFAULT_SWIPE_DURATION,
+ Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), 0));
+ if (mCurrentShift.getCurrentAnimation() != null) {
+ ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
+ long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
+
+ // TODO: Find a better heuristic
+ duration = (duration + theirDuration) / 2;
+ }
+ ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
+ .setDuration(duration);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+ }
+ });
+ anim.start();
+ }
+
+ public void setTaskSnapshot(Bitmap taskSnapshot) {
+ mTaskSnapshot = taskSnapshot;
}
@Override
public void onLauncherResume() {
- mStartDelta = mCurrentDisplacement;
- mLastDelta = mStartDelta;
- mChoreographer = Choreographer.getInstance();
-
- scheduleNextFrame();
+ TraceHelper.partitionSection("TouchInt", "Launcher On resume");
+ mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mStateCallback.setState(STATE_LAUNCHER_READY);
+ TraceHelper.partitionSection("TouchInt", "Launcher drawn");
+ return true;
+ }
+ });
}
@Override
- public void onCreate(Launcher launcher) {
+ protected void init(Launcher launcher, boolean alreadyOnHome) {
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
+
mLauncher = launcher;
mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
mLauncher.getDragLayer().addView(mDragView);
@@ -130,154 +181,113 @@
// Optimization
mLauncher.getAppsView().setVisibility(View.GONE);
-
- // Launch overview
- mRecentsView.update(consumeLastLoadPlan());
- mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, false /* animate */);
+ mRecentsView.setVisibility(View.GONE);
+ TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
}
- @Override
- public void onNewIntent(Launcher launcher, boolean alreadyOnHome) {
- mLauncher = launcher;
- mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
- mLauncher.getDragLayer().addView(mDragView);
- mDragView.setPivotX(0);
- mDragView.setPivotY(0);
- mRecentsView = mLauncher.getOverviewPanel();
- mHotseat = mLauncher.getHotseat();
-
- // Optimization
- mLauncher.getAppsView().setVisibility(View.GONE);
-
- // Launch overview, animate if already on home
- mRecentsView.update(consumeLastLoadPlan());
- mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
- }
-
- /**
- * This is updated on the binder thread and is picked up on the UI thread during the next
- * scheduled frame.
- * TODO: Instead of continuously scheduling frames, post the motion events to UI thread
- * (can ignore all continuous move events until the last move).
- */
- @BinderThread
+ @UiThread
public void updateDisplacement(float displacement) {
mCurrentDisplacement = displacement;
+ executeFrameUpdate();
}
- @BinderThread
- public void endTouch(float endVelocity) {
- mEndVelocity = endVelocity;
- mTouchEnded = true;
- }
-
- @UiThread
- private void scheduleNextFrame() {
- if (!mTouchEnded) {
- mChoreographer.postFrameCallback(this);
- } else {
- animateToFinalShift();
+ private void executeFrameUpdate() {
+ if (mLauncherReady) {
+ final float displacement = -mCurrentDisplacement;
+ int hotseatHeight = mHotseat.getHeight();
+ float translation = Utilities.boundToRange(displacement, 0, hotseatHeight);
+ float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
+ mCurrentShift.updateValue(shift);
}
}
- @Override
- public void doFrame(long l) {
- mLastDelta = mCurrentDisplacement;
-
- float translation = Utilities.boundToRange(mStartDelta - mLastDelta, 0,
- mHotseat.getHeight());
- int hotseatHeight = mHotseat.getHeight();
- float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
- setShift(shift);
- scheduleNextFrame();
- }
-
@UiThread
- private void setShift(float shift) {
+ private void updateFinalShift() {
+ if (!mLauncherReady) {
+ return;
+ }
+
if (mTargetRect.isEmpty()) {
DragLayer dl = mLauncher.getDragLayer();
-
- // Init target rect.
- View targetView = ((ViewGroup) mRecentsView.getChildAt(0)).getChildAt(0);
- dl.getViewRectRelativeToSelf(targetView, mTargetRect);
- mTargetRect.right = mTargetRect.left + mTargetRect.width();
- mTargetRect.bottom = mTargetRect.top + mTargetRect.height();
mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
+ Rect targetPadding = RecentsView.getPadding(mLauncher);
+ Rect insets = dl.getInsets();
+ mTargetRect.set(
+ targetPadding.left + insets.left,
+ targetPadding.top + insets.top,
+ mSourceRect.right - targetPadding.right - insets.right,
+ mSourceRect.bottom - targetPadding.bottom - insets.bottom);
+ mTargetRect.top += mLauncher.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
}
- if (!mSourceRect.isEmpty()) {
- mCurrentShift = shift;
- int hotseatHeight = mHotseat.getHeight();
- mHotseat.setTranslationY((1 - shift) * hotseatHeight);
+ float shift = mCurrentShift.value * mActivityMultiplier.value;
+ int hotseatHeight = mHotseat.getHeight();
- mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+ mHotseat.setTranslationY((1 - shift) * hotseatHeight);
- float scale = (float) mCurrentRect.width() / mSourceRect.width();
- mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
- mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
- mDragView.setScaleX(scale);
- mDragView.setScaleY(scale);
- mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
- mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
- }
- }
+ mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
- void setLastLoadPlan(Future<RecentsTaskLoadPlan> futureLoadPlan) {
- if (mFutureLoadPlan != null) {
- mFutureLoadPlan.cancel(true);
- }
- mFutureLoadPlan = futureLoadPlan;
- }
-
- private RecentsTaskLoadPlan consumeLastLoadPlan() {
- try {
- if (mFutureLoadPlan != null) {
- return mFutureLoadPlan.get();
- }
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- } finally {
- mFutureLoadPlan = null;
- }
- return null;
+ float scale = (float) mCurrentRect.width() / mSourceRect.width();
+ mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
+ mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
+ mDragView.setScaleX(scale);
+ mDragView.setScaleY(scale);
+ mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
+ mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
}
@UiThread
- private void animateToFinalShift() {
- float flingThreshold = Utilities.pxFromDp(FLING_THRESHOLD_VELOCITY,
- mLauncher.getResources().getDisplayMetrics());
- boolean isFling = Math.abs(mEndVelocity) > flingThreshold;
+ public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ mLoadPlan = loadPlan;
+ mStateCallback.setState(STATE_LOAD_PLAN_READY);
+ }
- long duration = 200;
+ private void setTaskPlanToUi() {
+ mRecentsView.update(mLoadPlan);
+ mRecentsView.setVisibility(View.VISIBLE);
+
+ // Animate alpha
+ mRecentsView.setAlpha(0);
+ mRecentsView.animate().alpha(1).setDuration(RECENTS_VIEW_VISIBILITY_DURATION)
+ .withEndAction(() -> mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE));
+ }
+
+ @UiThread
+ public void endTouch(float endVelocity) {
+ if (mTouchEndHandled) {
+ return;
+ }
+ mTouchEndHandled = true;
+
+ Resources res = mContext.getResources();
+ float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = Math.abs(endVelocity) > flingThreshold;
+
+ long duration = DEFAULT_SWIPE_DURATION;
final float endShift;
if (!isFling) {
- endShift = mCurrentShift >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
+ endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
} else {
- endShift = mEndVelocity < 0 ? 1 : 0;
- float minFlingVelocity = Utilities.pxFromDp(MIN_FLING_VELOCITY,
- mLauncher.getResources().getDisplayMetrics());
- if (Math.abs(mEndVelocity) > minFlingVelocity) {
- float distanceToTravel = (endShift - mCurrentShift) * mHotseat.getHeight();
+ endShift = endVelocity < 0 ? 1 : 0;
+ float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
+ float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
- // derivative of the scroll interpolator at zero, ie. 5. We use 4 to make
- // it a little slower.
- duration = 4 * Math.round(1000 * Math.abs(distanceToTravel / mEndVelocity));
+ // derivative of the scroll interpolator at zero, ie. 5.
+ duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
}
}
- ObjectAnimator anim = ObjectAnimator.ofFloat(this, SHIFT, endShift)
- .setDuration(duration);
+ ObjectAnimator anim = mCurrentShift.animateToValue(endShift).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- if (Float.compare(mCurrentShift, 0) == 0) {
- resumeLastTask();
- } else {
- mDragView.close(false);
- }
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
}
});
anim.start();
@@ -285,12 +295,30 @@
@UiThread
private void resumeLastTask() {
+ TaskKey key = null;
+ if (mLoadPlan != null) {
+ Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+ if (task != null) {
+ key = task.key;
+ }
+ }
+
+ if (key == null) {
+ // TODO: We need a better way for this
+ key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
+ }
+
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
+ }
+
+ private void cleanupLauncher() {
// TODO: These should be done as part of ActivityOptions#OnAnimationStarted
mHotseat.setTranslationY(0);
mLauncher.setOnResumeCallback(() -> mDragView.close(false));
+ }
- // TODO: For now, assume that the task stack will have loaded in the bg, will update
- // the lib api later for direct call
- mRecentsView.launchTaskWithId(mRunningTaskId);
+ private void onAnimationToLauncherComplete() {
+ mDragView.close(false);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index a0340b6..f92d773 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,6 +22,7 @@
import android.widget.ArrayAdapter;
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;
@@ -37,7 +38,8 @@
super.onCreate(savedInstanceState);
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
- plan.preloadPlan(new RecentsTaskLoader(this, 1, 1, 0), -1, UserHandle.myUserId());
+ plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+ UserHandle.myUserId());
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
mAdapter.addAll(plan.getTaskStack().getTasks());
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 528b11d..ba88f99 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -16,10 +16,12 @@
package com.android.quickstep;
+import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -41,6 +43,12 @@
*/
public class RecentsView extends PagedView {
+ /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
+ private 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. */
+ private static final TimeInterpolator CURVE_INTERPOLATOR
+ = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+
private boolean mOverviewStateEnabled;
private boolean mTaskStackListenerRegistered;
@@ -69,23 +77,14 @@
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
+ enableFreeScroll(true);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- // TODO: These are rough calculations which currently use the stable insets
- DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
- Rect stableInsets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
- Rect padding = profile.getWorkspacePadding(null);
- float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
- float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
- float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- - stableInsets.top;
- float overviewWidth = taskWidth * overviewHeight / taskHeight;
- padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ Rect padding = getPadding(Launcher.getLauncher(getContext()));
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
@@ -166,4 +165,53 @@
mTaskStackListenerRegistered = registerStackListener;
}
}
+
+ public static Rect getPadding(Launcher launcher) {
+ DeviceProfile profile = launcher.getDeviceProfile();
+ Rect stableInsets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+ Rect padding = profile.getWorkspacePadding(null);
+ float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
+ float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
+ float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - stableInsets.top;
+ float overviewWidth = taskWidth * overviewHeight / taskHeight;
+ padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ return padding;
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ super.scrollTo(x, y);
+ updateCurveProperties();
+ }
+
+ /**
+ * Scales and adjusts translation of adjacent pages as if on a curved carousel.
+ */
+ private void updateCurveProperties() {
+ if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
+ return;
+ }
+ final int halfScreenWidth = getMeasuredWidth() / 2;
+ final int screenCenter = halfScreenWidth + getScrollX();
+ final int pageSpacing = getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ final int pageCount = getPageCount();
+ for (int i = 0; i < pageCount; i++) {
+ View page = getPageAt(i);
+ int pageWidth = page.getMeasuredWidth();
+ int halfPageWidth = pageWidth / 2;
+ int pageCenter = page.getLeft() + halfPageWidth;
+ float distanceFromScreenCenter = Math.abs(pageCenter - screenCenter);
+ float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+ float linearInterpolation = Math.min(1, distanceFromScreenCenter / distanceToReachEdge);
+ float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
+ float scale = 1 - curveInterpolation * CURVE_FACTOR;
+ page.setScaleX(scale);
+ page.setScaleY(scale);
+ // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
+ page.setTranslationZ(scale);
+ page.setTranslationX((screenCenter - pageCenter) * curveInterpolation * CURVE_FACTOR);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 55d22e0..6e8bbeb 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -50,7 +50,7 @@
protected Paint mBgFillPaint = new Paint();
protected BitmapShader mBitmapShader;
- private float mDimAlpha;
+ private float mDimAlpha = 1f;
private LightingColorFilter mLightingColorFilter = new LightingColorFilter(Color.WHITE, 0);
public TaskThumbnailView(Context context) {
@@ -64,7 +64,6 @@
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
- setDimAlpha(1f);
setClipToOutline(true);
}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index f6408a8..029afd6 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -55,7 +55,6 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- setWillNotDraw(false);
setOnClickListener((view) -> {
launchTask(true /* animate */);
});
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1218176..55fd448 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -17,8 +17,6 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
-import static com.android.launcher3.states.InternalStateHandler.EXTRA_STATE_HANDLER;
-
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.Service;
@@ -31,29 +29,28 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
+import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
-import java.util.concurrent.Future;
-
/**
* Service connected by system-UI for handling touch interaction.
*/
@@ -67,7 +64,7 @@
@Override
public void onMotionEvent(MotionEvent ev) {
- handleMotionEvent(ev);
+ mEventQueue.queue(ev);
}
@Override
@@ -80,8 +77,11 @@
private RunningTaskInfo mRunningTask;
private Intent mHomeIntent;
private ComponentName mLauncher;
+ private MotionEventQueue mEventQueue;
+ private MainThreadExecutor mMainThreadExecutor;
-
+ private int mDisplayRotation;
+ private final Point mDisplaySize = new Point();
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
@@ -111,6 +111,9 @@
res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0);
sRecentsTaskLoader.startLoader(this);
}
+
+ mMainThreadExecutor = new MainThreadExecutor();
+ mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
}
@Override
@@ -129,10 +132,14 @@
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
+ TraceHelper.beginSection("TouchInt");
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+ Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+ display.getRealSize(mDisplaySize);
+ mDisplayRotation = display.getRotation();
mRunningTask = mAM.getRunningTask();
if (mRunningTask == null || mRunningTask.topActivity.equals(mLauncher)) {
@@ -188,6 +195,7 @@
case MotionEvent.ACTION_CANCEL:
// TODO: Should be different than ACTION_UP
case MotionEvent.ACTION_UP: {
+ TraceHelper.endSection("TouchInt");
endInteraction();
break;
@@ -197,45 +205,39 @@
private void startTouchTracking() {
// Create the shared handler
- mInteractionHandler = new NavBarSwipeInteractionHandler(getCurrentTaskSnapshot(),
- mRunningTask);
+ final NavBarSwipeInteractionHandler handler =
+ new NavBarSwipeInteractionHandler(mRunningTask, this);
// Preload and start the recents activity on a background thread
final Context context = this;
- final int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().id;
final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context);
- Future<RecentsTaskLoadPlan> loadPlanFuture = BackgroundExecutor.get().submit(() -> {
- // Preload the plan
- RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
- loadPlan.preloadPlan(loader, runningTaskId, UserHandle.myUserId());
+ final int taskId = mRunningTask.id;
+ TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
- // Pass the
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_STATE_HANDLER, mInteractionHandler);
+ BackgroundExecutor.get().submit(() -> {
+ // Get the snap shot before
+ handler.setTaskSnapshot(getCurrentTaskSnapshot());
- // Start the activity
- Intent homeIntent = new Intent(mHomeIntent);
- homeIntent.putExtras(extras);
+ // Start the launcher activity with our custom handler
+ Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+ TraceHelper.partitionSection("TouchInt", "Home started");
+
/*
ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
null, null);
*/
- // Kick off loading of the plan while the activity is starting
- Options loadOpts = new Options();
- loadOpts.runningTaskId = runningTaskId;
- loadOpts.loadIcons = true;
- loadOpts.loadThumbnails = true;
- loadOpts.numVisibleTasks = 2;
- loadOpts.numVisibleTaskThumbnails = 2;
- loadOpts.onlyLoadForCache = false;
- loadOpts.onlyLoadPausedActivities = false;
- loader.loadTasks(loadPlan, loadOpts);
- }, loadPlan);
-
- mInteractionHandler.setLastLoadPlan(loadPlanFuture);
+ // Preload the plan
+ RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+ PreloadOptions opts = new PreloadOptions();
+ opts.loadTitles = false;
+ loadPlan.preloadPlan(opts, loader, taskId, UserHandle.myUserId());
+ // Set the load plan on UI thread
+ mMainThreadExecutor.execute(() -> handler.setRecentsTaskLoadPlan(loadPlan));
+ });
+ mInteractionHandler = handler;
}
private void endInteraction() {
@@ -243,7 +245,7 @@
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
- mInteractionHandler.endTouch(mVelocityTracker.getXVelocity(mActivePointerId));
+ mInteractionHandler.endTouch(mVelocityTracker.getYVelocity(mActivePointerId));
mInteractionHandler = null;
}
mVelocityTracker.recycle();
@@ -255,17 +257,17 @@
Log.e(TAG, "Never received systemUIProxy");
return null;
}
- Display display = getSystemService(WindowManager.class).getDefaultDisplay();
- Point size = new Point();
- display.getRealSize(size);
+ TraceHelper.beginSection("TaskSnapshot");
// TODO: We are using some hardcoded layers for now, to best approximate the activity layers
try {
- return mISystemUiProxy.screenshot(new Rect(), size.x, size.y, 0, 100000, false,
- display.getRotation());
+ return mISystemUiProxy.screenshot(new Rect(), mDisplaySize.x, mDisplaySize.y, 0, 100000,
+ false, mDisplayRotation).toBitmap();
} catch (RemoteException e) {
Log.e(TAG, "Error capturing snapshot", e);
return null;
+ } finally {
+ TraceHelper.endSection("TaskSnapshot");
}
}
}
diff --git a/res/color/all_apps_tab_text.xml b/res/color/all_apps_tab_text.xml
new file mode 100644
index 0000000..f0c6310
--- /dev/null
+++ b/res/color/all_apps_tab_text.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?android:attr/colorAccent" android:state_selected="true"/>
+ <item android:color="?android:attr/textColorTertiary"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_notification_content.xml b/res/drawable/bg_notification_content.xml
new file mode 100644
index 0000000..cf129eb
--- /dev/null
+++ b/res/drawable/bg_notification_content.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="?attr/popupColorTertiary" />
+
+ <item android:height="3dp" android:top="0dp">
+ <shape>
+ <gradient
+ android:angle="270"
+ android:endColor="@android:color/transparent"
+ android:startColor="#33000000"
+ android:type="linear" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 05f509f..c42c15c 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -35,27 +35,34 @@
android:id="@+id/all_apps_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clickable="true"
- android:paddingTop="30dp"
+ android:paddingTop="@dimen/all_apps_header_top_padding"
+ android:clipToPadding="false"
android:layout_below="@id/search_container_all_apps" >
<com.android.launcher3.allapps.PredictionRowView
android:id="@+id/header_content"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content" />
- <LinearLayout
- android:id="@+id/tab_layout"
+ <include layout="@layout/all_apps_divider"
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@+id/tabs" />
+
+ <com.android.launcher3.views.SlidingTabStrip
+ android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_tab_height"
android:layout_below="@id/header_content"
- android:orientation="horizontal">
+ android:orientation="horizontal" >
<Button
android:id="@+id/tab_personal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/all_apps_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
android:background="?android:attr/selectableItemBackground"/>
<Button
android:id="@+id/tab_work"
@@ -63,9 +70,9 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/all_apps_work_tab"
+ android:textColor="@color/all_apps_tab_text"
android:background="?android:attr/selectableItemBackground"/>
- </LinearLayout>
-
+ </com.android.launcher3.views.SlidingTabStrip>
</RelativeLayout>
<!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
index fa1d591..54a9b88 100644
--- a/res/layout/all_apps_tabs.xml
+++ b/res/layout/all_apps_tabs.xml
@@ -25,7 +25,7 @@
android:clipChildren="false"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
- android:paddingTop="30dp">
+ android:paddingTop="@dimen/all_apps_header_top_padding">
<include layout="@layout/all_apps_rv_layout" />
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
deleted file mode 100644
index 1eebb43..0000000
--- a/res/layout/notification.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.launcher3.notification.NotificationItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_view"
- android:layout_width="@dimen/bg_popup_item_width"
- android:layout_height="wrap_content"
- android:theme="@style/PopupItem"
- android:elevation="@dimen/deep_shortcuts_elevation">
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:clipChildren="false">
-
- <View
- android:id="@+id/gutter_top"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:theme="@style/PopupGutter"
- android:visibility="gone" />
-
- <FrameLayout
- android:id="@+id/header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- android:paddingStart="@dimen/notification_padding_start"
- android:paddingEnd="@dimen/notification_padding_end"
- android:background="?attr/popupColorPrimary"
- android:elevation="@dimen/notification_elevation"
- android:layout_below="@id/gutter_top" >
- <TextView
- android:id="@+id/notification_text"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="start"
- android:gravity="center_vertical"
- android:text="@string/notifications_header"
- android:textSize="@dimen/notification_header_text_size"
- android:textColor="?android:attr/textColorPrimary" />
- <TextView
- android:id="@+id/notification_count"
- android:layout_width="@dimen/notification_icon_size"
- android:layout_height="match_parent"
- android:layout_gravity="end"
- android:gravity="center"
- android:textSize="@dimen/notification_header_count_text_size"
- android:fontFamily="sans-serif-medium"
- android:textColor="?android:attr/textColorPrimary" />
- </FrameLayout>
-
- <include layout="@layout/notification_main"
- android:id="@+id/main_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_main_height"
- android:layout_below="@id/header" />
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="@dimen/popup_item_divider_height"
- android:background="?attr/popupColorTertiary"
- android:layout_below="@id/main_view"
- android:visibility="gone" />
-
- <include layout="@layout/notification_footer"
- android:id="@+id/footer"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_footer_height"
- android:layout_below="@id/divider" />
-
- <View
- android:id="@+id/gutter_bottom"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:theme="@style/PopupGutter"
- android:visibility="gone"
- android:layout_below="@id/footer" />
-
- </RelativeLayout>
-
-</com.android.launcher3.notification.NotificationItemView>
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
new file mode 100644
index 0000000..d01be01
--- /dev/null
+++ b/res/layout/notification_content.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- header -->
+ <FrameLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:paddingEnd="@dimen/notification_padding_end"
+ android:paddingStart="@dimen/notification_padding_start">
+ <TextView
+ android:id="@+id/notification_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:gravity="center_vertical"
+ android:text="@string/notifications_header"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_header_text_size" />
+ <TextView
+ android:id="@+id/notification_count"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_header_count_text_size" />
+ </FrameLayout>
+
+ <!-- Main view -->
+ <com.android.launcher3.notification.NotificationMainView
+ android:id="@+id/main_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_main_height"
+ android:background="@drawable/bg_notification_content"
+ android:focusable="true" >
+
+ <LinearLayout
+ android:id="@+id/text_and_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/popupColorPrimary"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingBottom="14dp"
+ android:paddingEnd="@dimen/notification_main_text_padding_end"
+ android:paddingStart="@dimen/notification_padding_start">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:lines="1"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/notification_main_title_size" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:lines="1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="@dimen/notification_main_text_size" />
+ </LinearLayout>
+
+ <View
+ android:id="@+id/popup_item_icon"
+ android:layout_width="@dimen/notification_icon_size"
+ android:layout_height="@dimen/notification_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginBottom="7dp"
+ android:layout_marginEnd="@dimen/notification_padding_end" />
+
+ </com.android.launcher3.notification.NotificationMainView>
+
+ <!-- Divider -->
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/popup_item_divider_height"
+ android:layout_below="@id/main_view"
+ android:background="?attr/popupColorTertiary" />
+
+ <!-- Footer -->
+ <com.android.launcher3.notification.NotificationFooterLayout
+ android:id="@+id/footer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_footer_height"
+ android:layout_gravity="center_vertical"
+ android:clipChildren="false">
+
+ <LinearLayout
+ android:id="@+id/icon_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:padding="@dimen/notification_footer_icon_row_padding"/>
+
+ <View
+ android:id="@+id/overflow"
+ android:layout_width="@dimen/horizontal_ellipsis_size"
+ android:layout_height="@dimen/horizontal_ellipsis_size"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+ android:background="@drawable/horizontal_ellipsis" />
+
+ </com.android.launcher3.notification.NotificationFooterLayout>
+</merge>
\ No newline at end of file
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
deleted file mode 100644
index 86280e0..0000000
--- a/res/layout/notification_footer.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<com.android.launcher3.notification.NotificationFooterLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:elevation="@dimen/notification_elevation"
- android:clipChildren="false"
- android:layout_gravity="center_vertical"
- android:background="?attr/popupColorPrimary">
-
- <LinearLayout
- android:id="@+id/icon_row"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
- android:gravity="end|center_vertical"
- android:padding="@dimen/notification_footer_icon_row_padding"
- android:clipToPadding="false"
- android:clipChildren="false"/>
-
- <View
- android:id="@+id/overflow"
- android:layout_width="@dimen/horizontal_ellipsis_size"
- android:layout_height="@dimen/horizontal_ellipsis_size"
- android:background="@drawable/horizontal_ellipsis"
- android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
- android:layout_gravity="start|center_vertical" />
-
-</com.android.launcher3.notification.NotificationFooterLayout>
-
diff --git a/res/layout/notification_gutter.xml b/res/layout/notification_gutter.xml
new file mode 100644
index 0000000..10e7f7d
--- /dev/null
+++ b/res/layout/notification_gutter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="4dp"
+ android:layout_marginTop="4dp"
+ android:background="@drawable/bg_notification_content" />
\ No newline at end of file
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
deleted file mode 100644
index f94face..0000000
--- a/res/layout/notification_main.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<com.android.launcher3.notification.NotificationMainView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:elevation="@dimen/notification_elevation" >
-
- <LinearLayout
- android:id="@+id/text_and_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:background="?attr/popupColorPrimary"
- android:paddingStart="@dimen/notification_padding_start"
- android:paddingEnd="@dimen/notification_main_text_padding_end"
- android:paddingBottom="14dp">
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/notification_main_title_size"
- android:textColor="?android:attr/textColorPrimary"
- android:lines="1"
- android:ellipsize="end" />
-
- <TextView
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/notification_main_text_size"
- android:textColor="?android:attr/textColorSecondary"
- android:lines="1"
- android:ellipsize="end" />
- </LinearLayout>
-
- <View
- android:id="@+id/popup_item_icon"
- android:layout_width="@dimen/notification_icon_size"
- android:layout_height="@dimen/notification_icon_size"
- android:layout_marginEnd="@dimen/notification_padding_end"
- android:layout_marginBottom="7dp"
- android:layout_gravity="center_vertical|end" />
-
-</com.android.launcher3.notification.NotificationMainView>
-
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index 67db4a5..c737407 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,11 +19,8 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
+ android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical">
-
-</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
+ android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/shortcuts_item.xml b/res/layout/shortcuts_item.xml
deleted file mode 100644
index 7cd996d..0000000
--- a/res/layout/shortcuts_item.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.launcher3.shortcuts.ShortcutsItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/shortcuts_view"
- android:layout_width="@dimen/bg_popup_item_width"
- android:layout_height="wrap_content"
- android:elevation="@dimen/deep_shortcuts_elevation">
-
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <!-- The shortcuts header is added at runtime when necessary. -->
-
- <LinearLayout
- android:id="@+id/shortcuts"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" />
- </LinearLayout>
-
-</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
index 34d63e7..4daf469 100644
--- a/res/layout/system_shortcut_icons.xml
+++ b/res/layout/system_shortcut_icons.xml
@@ -22,6 +22,4 @@
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:background="?attr/popupColorSecondary"
- android:elevation="1dp"
- android:outlineProvider="none" />
- <!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
+ android:clipToPadding="true" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 94db0cc..266e0b0 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -91,6 +91,9 @@
<dimen name="all_apps_background_canvas_height">475dp</dimen>
<dimen name="all_apps_caret_workspace_offset">18dp</dimen>
<dimen name="all_apps_header_tab_height">50dp</dimen>
+ <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
+ <dimen name="all_apps_header_top_padding">36dp</dimen>
+ <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
@@ -180,6 +183,7 @@
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
<dimen name="popup_padding_start">10dp</dimen>
<dimen name="popup_padding_end">16dp</dimen>
+ <dimen name="popup_vertical_padding">4dp</dimen>
<dimen name="popup_arrow_width">10dp</dimen>
<dimen name="popup_arrow_height">8dp</dimen>
<dimen name="popup_arrow_vertical_offset">-2dp</dimen>
@@ -227,7 +231,6 @@
<dimen name="notification_footer_icon_size">18dp</dimen>
<!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
<dimen name="notification_main_text_padding_end">52dp</dimen>
- <dimen name="notification_elevation">2dp</dimen>
<dimen name="horizontal_ellipsis_size">18dp</dimen>
<!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
<dimen name="horizontal_ellipsis_offset">19dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8129e81..8cc4743 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -136,13 +136,6 @@
<style name="PopupItem">
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
</style>
- <style name="PopupGutter">
- <item name="android:backgroundTintMode">multiply</item>
- <item name="android:backgroundTint">?attr/popupColorSecondary</item>
- <item name="android:background">@drawable/gutter_horizontal</item>
- <item name="android:elevation">@dimen/notification_elevation</item>
- <item name="android:outlineProvider">none</item>
- </style>
<!-- Drop targets -->
<style name="DropTargetButtonBase">
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index d8c4efa..cfb55cc 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -53,6 +53,7 @@
public abstract class ButtonDropTarget extends TextView
implements DropTarget, DragController.DragListener, OnClickListener {
+ private static final int[] sTempCords = new int[2];
private static final int DRAG_VIEW_DROP_DURATION = 285;
private final boolean mHideParentOnDisable;
@@ -257,9 +258,9 @@
super.getHitRect(outRect);
outRect.bottom += mBottomDragPadding;
- int[] coords = new int[2];
- mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
- outRect.offsetTo(coords[0], coords[1]);
+ sTempCords[0] = sTempCords[1] = 0;
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords);
+ outRect.offsetTo(sTempCords[0], sTempCords[1]);
}
public Rect getIconRect(DragObject dragObject) {
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index be76490..60f5ca2 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -9,8 +9,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
-public class InsettableFrameLayout extends FrameLayout implements
- ViewGroup.OnHierarchyChangeListener, Insettable {
+public class InsettableFrameLayout extends FrameLayout implements Insettable {
@ViewDebug.ExportedProperty(category = "launcher")
protected Rect mInsets = new Rect();
@@ -21,7 +20,6 @@
public InsettableFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- setOnHierarchyChangeListener(this);
}
public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
@@ -95,12 +93,8 @@
}
@Override
- public void onChildViewAdded(View parent, View child) {
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
setFrameLayoutChildInsets(child, mInsets, new Rect());
}
-
- @Override
- public void onChildViewRemoved(View parent, View child) {
- }
-
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 60c00fb..b7986da 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -60,6 +60,7 @@
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
@@ -107,7 +108,6 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.PinItemDragListener;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -118,12 +118,12 @@
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.popup.BaseActionPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.states.AllAppsState;
import com.android.launcher3.states.InternalStateHandler;
+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;
@@ -359,9 +359,27 @@
mPopupDataProvider = new PopupDataProvider(this);
- restoreState(savedInstanceState);
+ 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);
+ }
- InternalStateHandler.handleCreate(this, getIntent());
+ 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.
+ savedInstanceState.remove(RUNTIME_STATE);
+ }
+ }
+ restoreState(savedInstanceState);
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
@@ -369,10 +387,13 @@
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
+
if (!mModel.startLoader(currentScreen)) {
- // If we are not binding synchronously, show a fade in animation when
- // the first page bind completes.
- mDragLayer.setAlpha(0);
+ if (!internalStateHandled) {
+ // If we are not binding synchronously, show a fade in animation when
+ // the first page bind completes.
+ mDragLayer.setAlpha(0);
+ }
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
@@ -384,20 +405,6 @@
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
- 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);
- }
-
- if (PinItemDragListener.handleDragRequest(this, getIntent())) {
- // Temporarily enable the rotation
- mRotationEnabled = true;
- }
-
// 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
@@ -767,10 +774,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStop();
}
-
- if (Utilities.ATLEAST_NOUGAT_MR1) {
- mAppWidgetHost.stopListening();
- }
+ mAppWidgetHost.setListenIfResumed(false);
if (!mAppLaunchSuccess) {
getUserEventDispatcher().logActionCommand(Action.Command.STOP,
@@ -787,10 +791,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStart();
}
-
- if (Utilities.ATLEAST_NOUGAT_MR1) {
- mAppWidgetHost.startListening();
- }
+ mAppWidgetHost.setListenIfResumed(true);
if (!isWorkspaceLoading()) {
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@@ -1065,7 +1066,6 @@
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDragController.setMoveTarget(mWorkspace);
- mDragController.addDropTarget(mWorkspace);
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
@@ -1260,9 +1260,9 @@
mWorkspace.updateIconBadges(updatedBadges);
mAppsView.updateIconBadges(updatedBadges);
- BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this);
- if (popup instanceof PopupContainerWithArrow) {
- ((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges);
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
+ if (popup != null) {
+ popup.updateNotificationHeader(updatedBadges);
}
}
};
@@ -1349,65 +1349,47 @@
boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
&& AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+ boolean internalStateHandled = InternalStateHandler
+ .handleNewIntent(this, intent, alreadyOnHome);
+
if (isActionMain) {
- if (mWorkspace == null) {
- // Can be cases where mWorkspace is null, this prevents a NPE
- return;
- }
+ if (!internalStateHandled) {
+ // Note: There should be at most one log per method call. This is enforced
+ // implicitly by using if-else statements.
+ UserEventDispatcher ued = getUserEventDispatcher();
+ AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
+ if (topOpenView != null) {
+ topOpenView.logActionCommand(Action.Command.HOME_INTENT);
+ } else if (alreadyOnHome) {
+ Target target = newContainerTarget(mStateManager.getState().containerType);
+ target.pageIndex = mWorkspace.getCurrentPage();
+ ued.logActionCommand(Action.Command.HOME_INTENT, target);
+ }
- // Note: There should be at most one log per method call. This is enforced implicitly
- // by using if-else statements.
- UserEventDispatcher ued = getUserEventDispatcher();
- AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
- if (topOpenView != null) {
- topOpenView.logActionCommand(Action.Command.HOME_INTENT);
- } else if (alreadyOnHome) {
- Target target = newContainerTarget(mStateManager.getState().containerType);
- target.pageIndex = mWorkspace.getCurrentPage();
- ued.logActionCommand(Action.Command.HOME_INTENT, target);
- }
+ // In all these cases, only animate if we're already on home
+ AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
- // In all these cases, only animate if we're already on home
- AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
- mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
+ mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
+
+ // Reset the apps view
+ if (!alreadyOnHome && mAppsView != null) {
+ mAppsView.reset();
+ }
+
+ if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+ mWorkspace.post(mWorkspace::moveToDefaultScreen);
+ }
+ }
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
}
- // Reset the apps view
- if (!alreadyOnHome && mAppsView != null) {
- mAppsView.reset();
- }
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent();
}
}
- PinItemDragListener.handleDragRequest(this, intent);
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onNewIntent(intent);
- }
-
- // Defer moving to the default screen until after we callback to the LauncherCallbacks
- // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
- // animation.
- if (isActionMain) {
- if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
-
- mWorkspace.post(new Runnable() {
- @Override
- public void run() {
- if (mWorkspace != null) {
- mWorkspace.moveToDefaultScreen();
- }
- }
- });
- }
- }
- InternalStateHandler.handleNewIntent(this, intent, alreadyOnHome);
TraceHelper.endSection("NEW_INTENT");
}
@@ -2081,11 +2063,16 @@
intent.setPackage(pickerPackage);
}
- intent.setSourceBounds(getViewBounds(v));
+ final Bundle launchOptions;
+ if (v != null) {
+ intent.setSourceBounds(getViewBounds(v));
+ // If there is no target package, use the default intent chooser animation
+ launchOptions = hasTargetPackage ? getActivityLaunchOptions(v) : null;
+ } else {
+ launchOptions = null;
+ }
try {
- startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
- // If there is no target package, use the default intent chooser animation
- hasTargetPackage ? getActivityLaunchOptions(v) : null);
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER, launchOptions);
} catch (ActivityNotFoundException e) {
setWaitingForResult(null);
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -2241,7 +2228,7 @@
getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
Action.Direction.NONE, ContainerType.WORKSPACE,
mWorkspace.getCurrentPage());
- getStateManager().goToState(OVERVIEW);
+ UiFactory.onWorkspaceLongPress(this);
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
return true;
@@ -2278,7 +2265,7 @@
getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
Action.Direction.NONE, ContainerType.WORKSPACE,
mWorkspace.getCurrentPage());
- getStateManager().goToState(OVERVIEW);
+ UiFactory.onWorkspaceLongPress(this);
}
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3181,7 +3168,7 @@
&& mAccessibilityDelegate.performAction(focusedView,
(ItemInfo) focusedView.getTag(),
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
- BaseActionPopup.getOpen(this).requestFocus();
+ PopupContainerWithArrow.getOpen(this).requestFocus();
return true;
}
break;
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 70440fa..9aa74b3 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -16,7 +16,8 @@
package com.android.launcher3;
-import android.app.Activity;
+import static android.app.Activity.RESULT_CANCELED;
+
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
@@ -41,12 +42,17 @@
*/
public class LauncherAppWidgetHost extends AppWidgetHost {
+ private static final int FLAG_LISTENING = 1;
+ private static final int FLAG_RESUMED = 1 << 1;
+ private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2;
+
public static final int APPWIDGET_HOST_ID = 1024;
private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
private final Context mContext;
+ private int mFlags = FLAG_RESUMED;
public LauncherAppWidgetHost(Context context) {
super(context, APPWIDGET_HOST_ID);
@@ -66,7 +72,7 @@
if (FeatureFlags.GO_DISABLE_WIDGETS) {
return;
}
-
+ mFlags |= FLAG_LISTENING;
try {
super.startListening();
} catch (Exception e) {
@@ -85,10 +91,59 @@
if (FeatureFlags.GO_DISABLE_WIDGETS) {
return;
}
-
+ mFlags &= ~FLAG_LISTENING;
super.stopListening();
}
+ /**
+ * Updates the resumed state of the host.
+ * When a host is not resumed, it defers calls to startListening until host is resumed again.
+ * But if the host was already listening, it will not call stopListening.
+ *
+ * @see #setListenIfResumed(boolean)
+ */
+ public void setResumed(boolean isResumed) {
+ if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) {
+ return;
+ }
+ if (isResumed) {
+ mFlags |= FLAG_RESUMED;
+ // Start listening if we were supposed to start listening on resume
+ if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) {
+ startListening();
+ }
+ } else {
+ mFlags &= ~FLAG_RESUMED;
+ }
+ }
+
+ /**
+ * Updates the listening state of the host. If the host is not resumed, startListening is
+ * deferred until next resume.
+ *
+ * @see #setResumed(boolean)
+ */
+ public void setListenIfResumed(boolean listenIfResumed) {
+ if (!Utilities.ATLEAST_NOUGAT_MR1) {
+ return;
+ }
+ if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) {
+ return;
+ }
+ if (listenIfResumed) {
+ mFlags |= FLAG_LISTEN_IF_RESUMED;
+ if ((mFlags & FLAG_RESUMED) != 0) {
+ // If we are resumed, start listening immediately. Note we do not check for
+ // duplicate calls before calling startListening as startListening is safe to call
+ // multiple times.
+ startListening();
+ }
+ } else {
+ mFlags &= ~FLAG_LISTEN_IF_RESUMED;
+ stopListening();
+ }
+ }
+
@Override
public int allocateAppWidgetId() {
if (FeatureFlags.GO_DISABLE_WIDGETS) {
@@ -203,12 +258,7 @@
}
private void sendActionCancelled(final BaseActivity activity, final int requestCode) {
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- activity.onActivityResult(requestCode, Activity.RESULT_CANCELED, null);
- }
- });
+ new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null));
}
/**
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 78d753a..928258f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -45,7 +45,6 @@
void onPause();
void onDestroy();
void onSaveInstanceState(Bundle outState);
- void onNewIntent(Intent intent);
void onActivityResult(int requestCode, int resultCode, Intent data);
void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index f016e8d..1e6016b 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -25,7 +25,6 @@
import android.os.Looper;
import android.view.View;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -83,6 +82,8 @@
private StateHandler[] mStateHandlers;
private LauncherState mState = NORMAL;
+ private StateListener mStateListener;
+
public LauncherStateManager(Launcher l) {
mUiHandler = new Handler(Looper.getMainLooper());
mLauncher = l;
@@ -99,6 +100,10 @@
return mStateHandlers;
}
+ public void setStateListener(StateListener stateListener) {
+ mStateListener = stateListener;
+ }
+
/**
* @see #goToState(LauncherState, boolean, Runnable)
*/
@@ -157,6 +162,9 @@
for (StateHandler handler : getStateHandlers()) {
handler.setState(state);
}
+ if (mStateListener != null) {
+ mStateListener.onStateSetImmediately(state);
+ }
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
// Run any queued runnable
@@ -210,6 +218,17 @@
public void onAnimationStart(Animator animation) {
// Change the internal state only when the transition actually starts
setState(state);
+ if (mStateListener != null) {
+ mStateListener.onStateTransitionStart(state);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mStateListener != null) {
+ mStateListener.onStateTransitionComplete(mState);
+ }
}
@Override
@@ -230,6 +249,7 @@
mState.onStateDisabled(mLauncher);
mState = state;
mState.onStateEnabled(mLauncher);
+ mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
}
/**
@@ -304,4 +324,15 @@
void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
AnimatorSet anim, AnimationConfig config);
}
+
+ public interface StateListener {
+
+ /**
+ * Called when the state is set without an animation.
+ */
+ void onStateSetImmediately(LauncherState state);
+
+ void onStateTransitionStart(LauncherState toState);
+ void onStateTransitionComplete(LauncherState finalState);
+ }
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4240a30..9f6efb3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -30,7 +30,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -87,13 +86,13 @@
public static final int INVALID_RESTORE_PAGE = -1001;
private boolean mFreeScroll = false;
+ private boolean mSettleOnPageInFreeScroll = false;
protected int mFlingThresholdVelocity;
protected int mMinFlingVelocity;
protected int mMinSnapVelocity;
protected boolean mFirstLayout = true;
- private int mNormalChildHeight;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mCurrentPage;
@@ -166,8 +165,6 @@
@Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
- private float mMinScale = 1f;
- private boolean mUseMinScale = false;
@Thunk View mDragView;
private Runnable mSidePageHoverRunnable;
@Thunk int mSidePageHoverIndex = -1;
@@ -273,12 +270,6 @@
}
}
- public void setMinScale(float f) {
- mMinScale = f;
- mUseMinScale = true;
- requestLayout();
- }
-
@Override
public void setScaleX(float scaleX) {
super.setScaleX(scaleX);
@@ -597,56 +588,9 @@
computeScrollHelper();
}
- public static class LayoutParams extends ViewGroup.LayoutParams {
- public boolean isFullScreenPage = false;
-
- // If true, the start edge of the page snaps to the start edge of the viewport.
- public boolean matchStartEdge = false;
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
-
- public void addFullScreenPage(View page) {
- LayoutParams lp = generateDefaultLayoutParams();
- lp.isFullScreenPage = true;
- super.addView(page, 0, lp);
- }
-
public int getNormalChildHeight() {
- return mNormalChildHeight;
+ return getViewportHeight() - getPaddingTop() - getPaddingBottom()
+ - mInsets.top - mInsets.bottom;
}
@Override
@@ -662,22 +606,7 @@
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
- // viewport, we can be at most one and a half screens offset once we scale down
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
- dm.heightPixels + mInsets.top + mInsets.bottom);
- int parentWidthSize = (int) (2f * maxSize);
- int parentHeightSize = (int) (2f * maxSize);
- int scaledWidthSize, scaledHeightSize;
- if (mUseMinScale) {
- scaledWidthSize = (int) (parentWidthSize / mMinScale);
- scaledHeightSize = (int) (parentHeightSize / mMinScale);
- } else {
- scaledWidthSize = widthSize;
- scaledHeightSize = heightSize;
- }
mViewport.set(0, 0, widthSize, heightSize);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
@@ -691,71 +620,19 @@
return;
}
- /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
- * of the All apps view on XLarge displays to not take up more space then it needs. Width
- * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
- * each page to have the same width.
- */
- final int verticalPadding = getPaddingTop() + getPaddingBottom();
- final int horizontalPadding = getPaddingLeft() + getPaddingRight();
-
- int referenceChildWidth = 0;
// The children are given the same width and height as the workspace
// unless they were set to WRAP_CONTENT
if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
- if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
- if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
- if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
- if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- // disallowing padding in paged view (just pass 0)
- final View child = getPageAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childWidthMode;
- int childHeightMode;
- int childWidth;
- int childHeight;
+ int myWidthSpec = MeasureSpec.makeMeasureSpec(
+ getViewportWidth() - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+ int myHeightSpec = MeasureSpec.makeMeasureSpec(
+ getViewportHeight() - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
- if (!lp.isFullScreenPage) {
- if (lp.width == LayoutParams.WRAP_CONTENT) {
- childWidthMode = MeasureSpec.AT_MOST;
- } else {
- childWidthMode = MeasureSpec.EXACTLY;
- }
-
- if (lp.height == LayoutParams.WRAP_CONTENT) {
- childHeightMode = MeasureSpec.AT_MOST;
- } else {
- childHeightMode = MeasureSpec.EXACTLY;
- }
-
- childWidth = getViewportWidth() - horizontalPadding
- - mInsets.left - mInsets.right;
- childHeight = getViewportHeight() - verticalPadding
- - mInsets.top - mInsets.bottom;
- mNormalChildHeight = childHeight;
- } else {
- childWidthMode = MeasureSpec.EXACTLY;
- childHeightMode = MeasureSpec.EXACTLY;
-
- childWidth = getViewportWidth();
- childHeight = getViewportHeight();
- }
- if (referenceChildWidth == 0) {
- referenceChildWidth = childWidth;
- }
-
- final int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
- final int childHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
- setMeasuredDimension(scaledWidthSize, scaledHeightSize);
+ // measureChildren takes accounts for content padding, we only need to care about extra
+ // space due to insets.
+ measureChildren(myWidthSpec, myHeightSpec);
+ setMeasuredDimension(widthSize, heightSize);
}
@SuppressLint("DrawAllocation")
@@ -780,10 +657,7 @@
int verticalPadding = getPaddingTop() + getPaddingBottom();
- LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
- LayoutParams nextLp;
-
- int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
+ int childLeft = offsetX + getPaddingLeft();
if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
mPageScrolls = new int[childCount];
}
@@ -791,14 +665,9 @@
for (int i = startIndex; i != endIndex; i += delta) {
final View child = getPageAt(i);
if (child.getVisibility() != View.GONE) {
- lp = (LayoutParams) child.getLayoutParams();
- int childTop;
- if (lp.isFullScreenPage) {
- childTop = offsetY;
- } else {
- childTop = offsetY + getPaddingTop() + mInsets.top;
- childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
- }
+ int childTop = offsetY + getPaddingTop() + mInsets.top;
+ childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding
+ - child.getMeasuredHeight()) / 2;
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
@@ -807,26 +676,10 @@
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(), childTop + childHeight);
- int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
+ int scrollOffsetLeft = getPaddingLeft();
mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
- int pageGap = mPageSpacing;
- int next = i + delta;
- if (next != endIndex) {
- nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
- } else {
- nextLp = null;
- }
-
- // Prevent full screen pages from showing in the viewport
- // when they are not the current page.
- if (lp.isFullScreenPage) {
- pageGap = getPaddingLeft();
- } else if (nextLp != null && nextLp.isFullScreenPage) {
- pageGap = getPaddingRight();
- }
-
- childLeft += childWidth + pageGap + getChildGap();
+ childLeft += childWidth + mPageSpacing + getChildGap();
}
}
@@ -1289,12 +1142,7 @@
} else {
View child = getChildAt(index);
- int scrollOffset = 0;
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isFullScreenPage) {
- scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
- }
-
+ int scrollOffset = scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
return (int) (child.getX() - baselineX);
}
@@ -1322,7 +1170,12 @@
* 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() {
@@ -1566,7 +1419,22 @@
mScroller.setInterpolator(mDefaultInterpolator);
mScroller.fling(initialScrollX,
getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
- mNextPage = getPageNearestToCenterOfScreen((int) (mScroller.getFinalX() / scaleX));
+ int unscaledScrollX = (int) (mScroller.getFinalX() / scaleX);
+ mNextPage = getPageNearestToCenterOfScreen(unscaledScrollX);
+ int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
+ int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
+ if (mSettleOnPageInFreeScroll && unscaledScrollX > firstPageScroll
+ && unscaledScrollX < lastPageScroll) {
+ // Make sure we land directly on a page. If flinging past one of the ends,
+ // don't change the velocity as it will get stopped at the end anyway.
+ mScroller.setFinalX((int) (getScrollForPage(mNextPage) * getScaleX()));
+ // Ensure the scroll/snap doesn't happen too fast;
+ int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
+ - mScroller.getDuration();
+ if (extraScrollDuration > 0) {
+ mScroller.extendDuration(extraScrollDuration);
+ }
+ }
invalidate();
}
onScrollInteractionEnd();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 93fe17c..0db5a16 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -80,7 +80,6 @@
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -440,10 +439,7 @@
setClipChildren(false);
setClipToPadding(false);
- // TODO: Remove this
- setMinScale(OverviewState.SCALE_FACTOR);
setupLayoutTransition();
-
mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
// Set the wallpaper dimensions when Launcher starts up
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 271a133..2bb95cb 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -58,8 +58,8 @@
import com.android.launcher3.util.ComponentKeyMapper;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.SlidingTabStrip;
import java.util.HashMap;
import java.util.List;
@@ -281,6 +281,9 @@
mAH[i].recyclerView.scrollToTop();
}
}
+ if (mFloatingHeaderHandler != null) {
+ mFloatingHeaderHandler.reset();
+ }
// Reset the search bar and base recycler view after transitioning home
mSearchUiManager.reset();
}
@@ -301,6 +304,7 @@
});
mHeader = findViewById(R.id.all_apps_header);
+ mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader);
rebindAdapters(mUsingTabs);
mSearchContainer = findViewById(R.id.search_container_all_apps);
@@ -431,10 +435,14 @@
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
setupWorkProfileTabs();
setupHeader();
- mHeader.setVisibility(View.VISIBLE);
} else {
- mHeader.setVisibility(View.GONE);
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
+ if (FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
+ setupHeader();
+ } else {
+ mFloatingHeaderHandler = null;
+ mHeader.setVisibility(View.GONE);
+ }
}
applyTouchDelegate();
@@ -471,6 +479,7 @@
}
private void setupWorkProfileTabs() {
+ final SlidingTabStrip tabs = findViewById(R.id.tabs);
mViewPager.setAdapter(new TabsPagerAdapter());
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@@ -478,6 +487,7 @@
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ tabs.updateIndicatorPosition(position, positionOffset);
if (positionOffset == 0 && !mVisible || positionOffset > 0 && mVisible) {
mVisible = positionOffset == 0;
for (int i = 0; i < mAH.length; i++) {
@@ -490,6 +500,7 @@
@Override
public void onPageSelected(int pos) {
+ tabs.updateTabTextColor(pos);
mFloatingHeaderHandler.setMainActive(pos == 0);
applyTouchDelegate();
if (mAH[pos].recyclerView != null) {
@@ -509,7 +520,7 @@
}
public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
- if (mUsingTabs) {
+ if (mFloatingHeaderHandler != null) {
mFloatingHeaderHandler.getContentView().setPredictedApps(apps);
}
mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
@@ -532,15 +543,24 @@
}
private void setupHeader() {
+ mHeader.setVisibility(View.VISIBLE);
int contentHeight = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+ if (!mUsingTabs) {
+ contentHeight += getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
+ }
RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
- RecyclerView workRV = mAH[AdapterHolder.WORK] != null
- ? mAH[AdapterHolder.WORK].recyclerView : null;
- mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader, mainRV, workRV, contentHeight);
- mFloatingHeaderHandler.getContentView().setNumAppsPerRow(mNumPredictedAppsPerRow);
- mFloatingHeaderHandler.getContentView().setComponentToAppMap(mComponentToAppMap);
+ RecyclerView workRV = mAH[AdapterHolder.WORK].recyclerView;
+ mFloatingHeaderHandler.setup(mainRV, workRV, contentHeight);
+ mFloatingHeaderHandler.getContentView().setup(mAH[AdapterHolder.MAIN].adapter,
+ mComponentToAppMap, mNumPredictedAppsPerRow);
+
+ int padding = contentHeight;
+ if (!mUsingTabs) {
+ padding += mHeader.getPaddingTop() + mHeader.getPaddingBottom();
+ }
for (int i = 0; i < mAH.length; i++) {
- mAH[i].paddingTopForTabs = contentHeight;
+ mAH[i].paddingTopForTabs = padding;
mAH[i].applyPadding();
}
}
@@ -553,7 +573,9 @@
public void onSearchResultsChanged() {
for (int i = 0; i < mAH.length; i++) {
- mAH[i].recyclerView.onSearchResultsChanged();
+ if (mAH[i].recyclerView != null) {
+ mAH[i].recyclerView.onSearchResultsChanged();
+ }
}
}
@@ -637,9 +659,14 @@
void applyPadding() {
if (recyclerView != null) {
- int paddingTop = mUsingTabs ? paddingTopForTabs : padding.top;
+ int paddingTop = mUsingTabs || FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW
+ ? paddingTopForTabs : padding.top;
recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
}
+ if (mFloatingHeaderHandler != null) {
+ mFloatingHeaderHandler.getContentView()
+ .setPadding(padding.left, 0 , padding.right, 0);
+ }
}
void applyNumsPerRow() {
@@ -649,7 +676,7 @@
}
adapter.setNumAppsPerRow(mNumAppsPerRow);
appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
- if (mUsingTabs && mFloatingHeaderHandler != null) {
+ if (mFloatingHeaderHandler != null) {
mFloatingHeaderHandler.getContentView()
.setNumAppsPerRow(mNumPredictedAppsPerRow);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index f9dde2f..7cf2d95 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -460,7 +460,7 @@
mFastScrollerSections.clear();
mAdapterItems.clear();
- if (!FeatureFlags.ALL_APPS_TABS_ENABLED) {
+ if (!FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
if (DEBUG_PREDICTIONS) {
if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
index 984966b..0b39b7d 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
@@ -15,36 +15,52 @@
*/
package com.android.launcher3.allapps;
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
import com.android.launcher3.R;
-public class FloatingHeaderHandler extends RecyclerView.OnScrollListener {
+public class FloatingHeaderHandler extends RecyclerView.OnScrollListener
+ implements ValueAnimator.AnimatorUpdateListener {
- private final int mMaxTranslation;
private final View mHeaderView;
- private final PredictionRowView mContentView;
- private final RecyclerView mMainRV;
- private final RecyclerView mWorkRV;
+ private final PredictionRowView mPredictionRow;
+ private final ViewGroup mTabLayout;
+ private final View mDivider;
private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
+ private RecyclerView mMainRV;
+ private RecyclerView mWorkRV;
+ private boolean mTopOnlyMode;
private boolean mHeaderHidden;
+ private int mMaxTranslation;
private int mSnappedScrolledY;
private int mTranslationY;
private int mMainScrolledY;
private int mWorkScrolledY;
private boolean mMainRVActive;
- public FloatingHeaderHandler(@NonNull View header, @NonNull RecyclerView personalRV,
- @Nullable RecyclerView workRV, int contentHeight) {
+ public FloatingHeaderHandler(@NonNull ViewGroup header) {
mHeaderView = header;
- mContentView = mHeaderView.findViewById(R.id.header_content);
- mContentView.getLayoutParams().height = contentHeight;
- mMaxTranslation = contentHeight;
+ mTabLayout = header.findViewById(R.id.tabs);
+ mDivider = header.findViewById(R.id.divider);
+ mPredictionRow = header.findViewById(R.id.header_content);
+ }
+
+ public void setup(@NonNull RecyclerView personalRV, @Nullable RecyclerView workRV,
+ int predictionRowHeight) {
+ mTopOnlyMode = workRV == null;
+ mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
+ mPredictionRow.getLayoutParams().height = predictionRowHeight;
+ mMaxTranslation = predictionRowHeight;
mMainRV = personalRV;
mMainRV.addOnScrollListener(this);
mWorkRV = workRV;
@@ -52,6 +68,18 @@
workRV.addOnScrollListener(this);
}
setMainActive(true);
+ setupDivider();
+ }
+
+ private void setupDivider() {
+ Resources res = mHeaderView.getResources();
+ int verticalGap = res.getDimensionPixelSize(R.dimen.all_apps_divider_margin_vertical);
+ int sideGap = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+ mDivider.setPadding(sideGap, verticalGap,sideGap, mTopOnlyMode ? verticalGap : 0);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mDivider.getLayoutParams();
+ lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
+ lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTopOnlyMode ? R.id.header_content : R.id.tabs);
+ mDivider.setLayoutParams(lp);
}
public void setMainActive(boolean active) {
@@ -65,7 +93,15 @@
}
public PredictionRowView getContentView() {
- return mContentView;
+ return mPredictionRow;
+ }
+
+ public ViewGroup getTabLayout() {
+ return mTabLayout;
+ }
+
+ public View getDivider() {
+ return mDivider;
}
@Override
@@ -75,27 +111,39 @@
return;
}
+ if (mAnimator.isStarted()) {
+ mAnimator.cancel();
+ }
+
int current = isMainRV
? (mMainScrolledY -= dy)
: (mWorkScrolledY -= dy);
- if (dy == 0) {
- setExpanded(true);
- } else {
- moved(current);
- apply();
- }
+ moved(current);
+ apply();
+ }
+
+ public void reset() {
+ mMainScrolledY = 0;
+ mWorkScrolledY = 0;
+ setExpanded(true);
+ }
+
+ private boolean canSnapAt(int currentScrollY) {
+ return !mTopOnlyMode || Math.abs(currentScrollY) <= mPredictionRow.getHeight();
}
private void moved(final int currentScrollY) {
if (mHeaderHidden) {
if (currentScrollY <= mSnappedScrolledY) {
- mSnappedScrolledY = currentScrollY;
+ if (canSnapAt(currentScrollY)) {
+ mSnappedScrolledY = currentScrollY;
+ }
} else {
mHeaderHidden = false;
}
mTranslationY = currentScrollY;
- } else {
+ } else if (!mHeaderHidden) {
mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
// update state vars
@@ -110,20 +158,36 @@
}
private void apply() {
+ int uncappedTranslationY = mTranslationY;
mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
- mHeaderView.setTranslationY(mTranslationY);
+ mPredictionRow.setTranslationY(uncappedTranslationY);
+ mTabLayout.setTranslationY(mTranslationY);
+ mDivider.setTranslationY(mTopOnlyMode ? uncappedTranslationY : mTranslationY);
mClip.top = mMaxTranslation + mTranslationY;
+ // clipping on a draw might cause additional redraw
mMainRV.setClipBounds(mClip);
if (mWorkRV != null) {
mWorkRV.setClipBounds(mClip);
}
}
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (!mTopOnlyMode && newState == RecyclerView.SCROLL_STATE_IDLE
+ && mTranslationY != -mMaxTranslation && mTranslationY != 0) {
+ float scroll = Math.abs(getCurrentScroll());
+ boolean expand = scroll > mMaxTranslation
+ ? Math.abs(mTranslationY) < mMaxTranslation / 2 : true;
+ setExpanded(expand);
+ }
+ }
+
private void setExpanded(boolean expand) {
int translateTo = expand ? 0 : -mMaxTranslation;
- mTranslationY = translateTo;
- apply();
-
+ mAnimator.setIntValues(mTranslationY, translateTo);
+ mAnimator.addUpdateListener(this);
+ mAnimator.setDuration(150);
+ mAnimator.start();
mHeaderHidden = !expand;
mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
}
@@ -136,4 +200,10 @@
return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
}
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mTranslationY = (Integer) animation.getAnimatedValue();
+ apply();
+ }
+
}
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index 5551f07..45ef6c1 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -21,9 +21,11 @@
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
@@ -43,6 +45,8 @@
private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
private final List<AppInfo> mPredictedApps = new ArrayList<>();
+ // This adapter is only used to create an identical item w/ same behavior as in the all apps RV
+ private AllAppsGridAdapter mAdapter;
public PredictionRowView(@NonNull Context context) {
this(context, null);
@@ -53,8 +57,11 @@
setOrientation(LinearLayout.HORIZONTAL);
}
- public void setComponentToAppMap(HashMap<ComponentKey, AppInfo> componentToAppMap) {
- this.mComponentToAppMap = componentToAppMap;
+ public void setup(AllAppsGridAdapter adapter,
+ HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+ mAdapter = adapter;
+ mComponentToAppMap = componentToAppMap;
+ mNumPredictedAppsPerRow = numPredictedAppsPerRow;
}
/**
@@ -64,10 +71,6 @@
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
}
- public void onAppsUpdated() {
- // TODO
- }
-
/**
* Returns the predicted apps.
*/
@@ -87,15 +90,35 @@
public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps);
+ mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+ onAppsUpdated();
+ }
- List<AppInfo> newPredictedApps = processPredictedAppComponents(apps);
- // We only need to do work if any of the visible predicted apps have changed.
- if (!newPredictedApps.equals(mPredictedApps)) {
- if (newPredictedApps.size() == mPredictedApps.size()) {
- swapInNewPredictedApps(newPredictedApps);
+ private void onAppsUpdated() {
+ if (getChildCount() != mNumPredictedAppsPerRow) {
+ while (getChildCount() > mNumPredictedAppsPerRow) {
+ removeViewAt(0);
+ }
+ while (getChildCount() < mNumPredictedAppsPerRow) {
+ AllAppsGridAdapter.ViewHolder holder = mAdapter
+ .onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON);
+ BubbleTextView icon = (BubbleTextView) holder.itemView;
+ LinearLayout.LayoutParams params =
+ new LayoutParams(0, icon.getLayoutParams().height);
+ params.weight = 1;
+ icon.setLayoutParams(params);
+ addView(icon);
+ }
+ }
+
+ for (int i = 0; i < getChildCount(); i++) {
+ BubbleTextView icon = (BubbleTextView) getChildAt(i);
+ icon.reset();
+ if (mPredictedApps.size() > i) {
+ icon.setVisibility(View.VISIBLE);
+ icon.applyFromApplicationInfo(mPredictedApps.get(i));
} else {
- // We need to update the appIndex of all the items.
- onAppsUpdated();
+ icon.setVisibility(View.INVISIBLE);
}
}
}
@@ -124,16 +147,4 @@
}
return predictedApps;
}
-
- /**
- * Swaps out the old predicted apps with the new predicted apps, in place. This optimization
- * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged.
- *
- * Note: This should only be called if the # of predicted apps is the same.
- * This method assumes that predicted apps are the first items in the adapter.
- */
- private void swapInNewPredictedApps(List<AppInfo> apps) {
- // TODO
- }
-
}
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index 51d00d9..1312da9 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -28,10 +28,6 @@
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
- public ValueAnimator createRevealAnimator(final View revealView) {
- return createRevealAnimator(revealView, false);
- }
-
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
ValueAnimator va =
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
@@ -39,8 +35,13 @@
va.addListener(new AnimatorListenerAdapter() {
private boolean mWasCanceled = false;
+ private boolean mIsClippedToOutline;
+ private ViewOutlineProvider mOldOutlineProvider;
public void onAnimationStart(Animator animation) {
+ mIsClippedToOutline = revealView.getClipToOutline();
+ mOldOutlineProvider = revealView.getOutlineProvider();
+
revealView.setOutlineProvider(RevealOutlineAnimation.this);
revealView.setClipToOutline(true);
if (shouldRemoveElevationDuringAnimation()) {
@@ -55,8 +56,8 @@
public void onAnimationEnd(Animator animation) {
if (!mWasCanceled) {
- revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
- revealView.setClipToOutline(false);
+ revealView.setOutlineProvider(mOldOutlineProvider);
+ revealView.setClipToOutline(mIsClippedToOutline);
if (shouldRemoveElevationDuringAnimation()) {
revealView.setTranslationZ(0);
}
diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
index d01b26c..9c09477 100644
--- a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
@@ -18,11 +18,6 @@
import android.graphics.Rect;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
/**
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
* and two {@link Rect}s.
@@ -37,21 +32,12 @@
private final Rect mStartRect;
private final Rect mEndRect;
- private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
-
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect) {
- this(startRadius, endRadius, startRect, endRect,
- ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
- }
-
- public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
- Rect endRect, int roundedCorners) {
mStartRadius = startRadius;
mEndRadius = endRadius;
mStartRect = startRect;
mEndRect = endRect;
- mRoundedCorners = roundedCorners;
}
@Override
@@ -65,13 +51,7 @@
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
- if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
- mOutline.top -= mOutlineRadius;
- }
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
- if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
- mOutline.bottom += mOutlineRadius;
- }
}
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 1924710..7cf3da0 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -61,4 +61,6 @@
// When enabled shows a work profile tab in all apps
public static final boolean ALL_APPS_TABS_ENABLED = false;
+ // When enabled prediction row is rendered as it's own custom view
+ public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = false;
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c843e72..db199c1 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -147,11 +147,12 @@
// Start home and pass the draw request params
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
img.getBitmap().getWidth(), img.getWidth());
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+
+ Intent homeIntent = listener.addToIntent(
+ new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
if (!getResources().getBoolean(R.bool.allow_rotation) &&
!Utilities.isAllowRotationPrefEnabled(this) &&
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 96aaee0..4629dad 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -16,22 +16,25 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.LauncherState.NORMAL;
+
import android.content.ClipDescription;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
-import android.os.Parcel;
import android.os.SystemClock;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.widget.PendingItemDragHelper;
import java.util.UUID;
@@ -39,7 +42,7 @@
/**
* {@link DragSource} for handling drop from a different window.
*/
-public abstract class BaseItemDragListener implements
+public abstract class BaseItemDragListener extends InternalStateHandler implements
View.OnDragListener, DragSource, DragOptions.PreDragCondition {
private static final String TAG = "BaseItemDragListener";
@@ -67,25 +70,16 @@
mId = UUID.randomUUID().toString();
}
- protected BaseItemDragListener(Parcel parcel) {
- mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
- mPreviewBitmapWidth = parcel.readInt();
- mPreviewViewWidth = parcel.readInt();
- mId = parcel.readString();
- }
-
- protected void writeToParcel(Parcel parcel, int i) {
- mPreviewRect.writeToParcel(parcel, i);
- parcel.writeInt(mPreviewBitmapWidth);
- parcel.writeInt(mPreviewViewWidth);
- parcel.writeString(mId);
- }
-
public String getMimeType() {
return MIME_TYPE_PREFIX + mId;
}
- public void setLauncher(Launcher launcher) {
+ @Override
+ public void init(Launcher launcher, boolean alreadyOnHome) {
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
+ launcher.getDragLayer().setOnDragListener(this);
+
mLauncher = launcher;
mDragController = launcher.getDragController();
}
@@ -182,4 +176,7 @@
mLauncher.getDragLayer().setOnDragListener(null);
}
}
+
+ @Override
+ public void onLauncherResume() { }
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 9402383..818cea7 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -605,29 +605,32 @@
}
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
- final Rect r = mRectTemp;
+ mDragObject.x = x;
+ mDragObject.y = y;
+ final Rect r = mRectTemp;
final ArrayList<DropTarget> dropTargets = mDropTargets;
final int count = dropTargets.size();
- for (int i=count-1; i>=0; i--) {
+ for (int i = count - 1; i >= 0; i--) {
DropTarget target = dropTargets.get(i);
if (!target.isDropEnabled())
continue;
target.getHitRectRelativeToDragLayer(r);
-
- mDragObject.x = x;
- mDragObject.y = y;
if (r.contains(x, y)) {
-
dropCoordinates[0] = x;
dropCoordinates[1] = y;
mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
-
return target;
}
}
- return null;
+ // Pass all unhandled drag to workspace. Workspace finds the correct
+ // cell layout to drop to in the existing drag/drop logic.
+ dropCoordinates[0] = x;
+ dropCoordinates[1] = y;
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(),
+ dropCoordinates);
+ return mLauncher.getWorkspace();
}
public void setWindowToken(IBinder token) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8189b23..9f9822c 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -703,13 +703,14 @@
}
@Override
- public void onChildViewAdded(View parent, View child) {
- super.onChildViewAdded(parent, child);
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
updateChildIndices();
}
@Override
- public void onChildViewRemoved(View parent, View child) {
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
updateChildIndices();
}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index b9d97ac..924bb4c 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -18,23 +18,18 @@
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
-import android.content.Intent;
import android.content.pm.LauncherApps.PinItemRequest;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.view.DragEvent;
import android.view.View;
import android.widget.RemoteViews;
import com.android.launcher3.DragSource;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -46,9 +41,7 @@
* in the source window and is passed on to the Launcher activity as an Intent extra.
*/
@TargetApi(Build.VERSION_CODES.O)
-public class PinItemDragListener extends BaseItemDragListener implements Parcelable {
-
- public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+public class PinItemDragListener extends BaseItemDragListener {
private final PinItemRequest mRequest;
@@ -58,22 +51,6 @@
mRequest = request;
}
- private PinItemDragListener(Parcel parcel) {
- super(parcel);
- mRequest = PinItemRequest.CREATOR.createFromParcel(parcel);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- super.writeToParcel(parcel, i);
- mRequest.writeToParcel(parcel, i);
- }
-
@Override
protected boolean onDragStart(DragEvent event) {
if (!mRequest.isValid()) {
@@ -126,33 +103,4 @@
}
return null;
}
-
- public static boolean handleDragRequest(Launcher launcher, Intent intent) {
- if (!Utilities.ATLEAST_OREO) {
- return false;
- }
- if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
- return false;
- }
- Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
- if (dragExtra instanceof PinItemDragListener) {
- PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
- dragListener.setLauncher(launcher);
-
- launcher.getDragLayer().setOnDragListener(dragListener);
- return true;
- }
- return false;
- }
-
- public static final Parcelable.Creator<PinItemDragListener> CREATOR =
- new Parcelable.Creator<PinItemDragListener>() {
- public PinItemDragListener createFromParcel(Parcel source) {
- return new PinItemDragListener(source);
- }
-
- public PinItemDragListener[] newArray(int size) {
- return new PinItemDragListener[size];
- }
- };
}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
index 150522e..938955c 100644
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -27,7 +27,7 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.popup.BaseActionPopup;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import java.util.ArrayList;
import java.util.Collections;
@@ -46,7 +46,7 @@
public CustomActionsPopup(Launcher launcher, View icon) {
mLauncher = launcher;
mIcon = icon;
- BaseActionPopup container = BaseActionPopup.getOpen(launcher);
+ PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
if (container != null) {
mDelegate = container.getAccessibilityDelegate();
} else {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 816c1d4..8640401 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -118,9 +118,9 @@
deepShortcutMap.clear();
}
- public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
- String[] args) {
- if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+ public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+ String[] args) {
+ if (Arrays.asList(args).contains("--proto")) {
dumpProto(prefix, fd, writer, args);
return;
}
@@ -219,7 +219,7 @@
targetList.addAll(workspaces.valueAt(i).getFlattenedList());
}
- if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+ if (Arrays.asList(args).contains("--debug")) {
for (int i = 0; i < targetList.size(); i++) {
writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 3cf3ff6..1216a27 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -23,22 +23,18 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.popup.BaseActionPopup;
-import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.Themes;
import java.util.ArrayList;
import java.util.Iterator;
@@ -61,11 +57,12 @@
private final List<NotificationInfo> mNotifications = new ArrayList<>();
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
private final boolean mRtl;
+ private final int mBackgroundColor;
FrameLayout.LayoutParams mIconLayoutParams;
private View mOverflowEllipsis;
private LinearLayout mIconRow;
- private int mBackgroundColor;
+ private NotificationItemView mContainer;
public NotificationFooterLayout(Context context) {
this(context, null, 0);
@@ -93,14 +90,19 @@
int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
- iconSize * MAX_FOOTER_NOTIFICATIONS;
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
+
+ mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mOverflowEllipsis = findViewById(R.id.overflow);
- mIconRow = (LinearLayout) findViewById(R.id.icon_row);
- mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
+ mIconRow = findViewById(R.id.icon_row);
+ }
+
+ void setContainer(NotificationItemView container) {
+ mContainer = container;
}
/**
@@ -198,25 +200,8 @@
updateOverflowEllipsisVisibility();
if (mIconRow.getChildCount() == 0) {
// There are no more icons in the footer, so hide it.
- BaseActionPopup popup = BaseActionPopup.getOpen(
- Launcher.getLauncher(getContext()));
- if (popup instanceof PopupContainerWithArrow) {
- final int newHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_empty_footer_height);
- Animator collapseFooter = ((PopupContainerWithArrow) popup)
- .reduceNotificationViewHeight(getHeight() - newHeight,
- getResources().getInteger(R.integer.config_removeNotificationViewDuration));
- collapseFooter.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- ((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
- // Keep view around because gutter is aligned to it, but remove height to
- // both hide the view and keep calculations correct for last dismissal.
- getLayoutParams().height = newHeight;
- requestLayout();
- }
- });
- collapseFooter.start();
+ if (mContainer != null) {
+ mContainer.removeFooter();
}
}
}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index ab94c32..5bbd19c 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,117 +16,102 @@
package com.android.launcher3.notification;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.app.Notification;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
-import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Themes;
import java.util.List;
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+
/**
- * A {@link FrameLayout} that contains a header, main view and a footer.
- * The main view contains the icon and text (title + subtext) of the first notification.
- * The footer contains: A list of just the icons of all the notifications past the first one.
- * @see NotificationFooterLayout
+ * Utility class to manage notification UI
*/
-public class NotificationItemView extends PopupItemView implements LogContainerProvider {
+public class NotificationItemView {
private static final Rect sTempRect = new Rect();
- private TextView mHeaderText;
- private TextView mHeaderCount;
- private NotificationMainView mMainView;
- private NotificationFooterLayout mFooter;
- private SwipeDetector mSwipeDetector;
+ private final Context mContext;
+ private final PopupContainerWithArrow mContainer;
+
+ private final TextView mHeaderText;
+ private final TextView mHeaderCount;
+ private final NotificationMainView mMainView;
+ private final NotificationFooterLayout mFooter;
+ private final SwipeDetector mSwipeDetector;
+ private final View mIconView;
+
+ private final View mHeader;
+ private final View mDivider;
+
+ private View mGutter;
+
+ private boolean mIgnoreTouch = false;
private boolean mAnimatingNextIcon;
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
- public NotificationItemView(Context context) {
- this(context, null, 0);
- }
+ public NotificationItemView(PopupContainerWithArrow container) {
+ mContainer = container;
+ mContext = container.getContext();
- public NotificationItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
+ mHeaderText = container.findViewById(R.id.notification_text);
+ mHeaderCount = container.findViewById(R.id.notification_count);
+ mMainView = container.findViewById(R.id.main_view);
+ mFooter = container.findViewById(R.id.footer);
+ mIconView = container.findViewById(R.id.popup_item_icon);
- public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
+ mHeader = container.findViewById(R.id.header);
+ mDivider = container.findViewById(R.id.divider);
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mHeaderText = findViewById(R.id.notification_text);
- mHeaderCount = findViewById(R.id.notification_count);
- mMainView = findViewById(R.id.main_view);
- mFooter = findViewById(R.id.footer);
-
- mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
+ mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
+ mFooter.setContainer(this);
}
- public NotificationMainView getMainView() {
- return mMainView;
+ public void addGutter() {
+ if (mGutter == null) {
+ mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
+ }
}
- /**
- * This method is used to calculate the height to remove when dismissing the last notification.
- * We subtract the height of the footer in this case since the footer should be gone or in the
- * process of being removed.
- * @return The height of the entire notification item, minus the footer if it still exists.
- */
- public int getHeightMinusFooter() {
- if (mFooter.getParent() == null) {
- return getHeight();
+ public void removeFooter() {
+ if (mContainer.indexOfChild(mFooter) >= 0) {
+ mContainer.removeView(mFooter);
+ mContainer.removeView(mDivider);
}
- int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
- R.dimen.notification_empty_footer_height);
- return getHeight() - excessFooterHeight;
}
- public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
- AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+ public void inverseGutterMargin() {
+ MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
+ int top = lp.topMargin;
+ lp.topMargin = lp.bottomMargin;
+ lp.bottomMargin = top;
+ }
- Rect startRect = new Rect(mPillRect);
- Rect endRect = new Rect(mPillRect);
- if (shouldRemoveFromTop) {
- endRect.top += heightToRemove;
- } else {
- endRect.bottom -= heightToRemove;
- }
- anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
- startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
+ public void removeAllViews() {
+ mContainer.removeView(mMainView);
+ mContainer.removeView(mHeader);
- View bottomGutter = findViewById(R.id.gutter_bottom);
- if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
- Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
- -heightToRemove);
- translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
- anim.play(translateGutter);
+ if (mContainer.indexOfChild(mFooter) >= 0) {
+ mContainer.removeView(mFooter);
+ mContainer.removeView(mDivider);
}
- return anim;
+ if (mGutter != null) {
+ mContainer.removeView(mGutter);
+ }
}
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
@@ -134,32 +119,44 @@
if (palette != null) {
if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
mNotificationHeaderTextColor =
- IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
- Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
+ IconPalette.resolveContrastColor(mContext, palette.dominantColor,
+ Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
}
mHeaderText.setTextColor(mNotificationHeaderTextColor);
mHeaderCount.setTextColor(mNotificationHeaderTextColor);
}
}
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
+ mMainView.getRight(), mMainView.getBottom());
+ mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
+ if (!mIgnoreTouch) {
+ mContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ if (mIgnoreTouch) {
+ return false;
+ }
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
- getParent().requestDisallowInterceptTouchEvent(true);
+
mSwipeDetector.onTouchEvent(ev);
return mSwipeDetector.isDraggingOrSettling();
}
- @Override
public boolean onTouchEvent(MotionEvent ev) {
+ if (mIgnoreTouch) {
+ return false;
+ }
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
- return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
+ return mSwipeDetector.onTouchEvent(ev);
}
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
@@ -168,7 +165,7 @@
}
NotificationInfo mainNotification = notificationInfos.get(0);
- mMainView.applyNotificationInfo(mainNotification, mIconView);
+ mMainView.applyNotificationInfo(mainNotification, false);
for (int i = 1; i < notificationInfos.size(); i++) {
mFooter.addNotificationInfo(notificationInfos.get(i));
@@ -182,29 +179,18 @@
if (dismissedMainNotification && !mAnimatingNextIcon) {
// Animate the next icon into place as the new main notification.
mAnimatingNextIcon = true;
- mMainView.setVisibility(INVISIBLE);
- mMainView.setTranslationX(0);
+ mMainView.setContentVisibility(View.INVISIBLE);
+ mMainView.setContentTranslation(0);
mIconView.getGlobalVisibleRect(sTempRect);
- mFooter.animateFirstNotificationTo(sTempRect,
- new NotificationFooterLayout.IconAnimationEndListener() {
- @Override
- public void onIconAnimationEnd(NotificationInfo newMainNotification) {
- if (newMainNotification != null) {
- mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
- mMainView.setVisibility(VISIBLE);
- }
- mAnimatingNextIcon = false;
+ mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
+ if (newMainNotification != null) {
+ mMainView.applyNotificationInfo(newMainNotification, true);
+ mMainView.setContentVisibility(View.VISIBLE);
}
+ mAnimatingNextIcon = false;
});
} else {
mFooter.trimNotifications(notificationKeys);
}
}
-
- @Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
- target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
- targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
- }
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index c8e1fdb..33caded 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -18,13 +18,17 @@
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -33,6 +37,7 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -42,13 +47,33 @@
* A {@link android.widget.FrameLayout} that contains a single notification,
* e.g. icon + title + text.
*/
+@TargetApi(Build.VERSION_CODES.N)
public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
+ private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
+ new FloatProperty<NotificationMainView>("contentTranslation") {
+ @Override
+ public void setValue(NotificationMainView view, float v) {
+ view.setContentTranslation(v);
+ }
+
+ @Override
+ public Float get(NotificationMainView view) {
+ return view.mTextAndBackground.getTranslationX();
+ }
+ };
+
+ // This is used only to track the notification view, so that it can be properly logged.
+ public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
+
+ private final ObjectAnimator mContentTranslateAnimator;
+
private NotificationInfo mNotificationInfo;
private ViewGroup mTextAndBackground;
private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
+ private View mIconView;
private SwipeDetector mSwipeDetector;
@@ -62,25 +87,24 @@
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+ mTextAndBackground = findViewById(R.id.text_and_background);
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
mBackgroundColor = colorBackground.getColor();
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
colorBackground, null);
mTextAndBackground.setBackground(rippleBackground);
- mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
- mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
- }
-
- public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
- applyNotificationInfo(mainNotification, iconView, false);
+ mTitleView = mTextAndBackground.findViewById(R.id.title);
+ mTextView = mTextAndBackground.findViewById(R.id.text);
+ mIconView = findViewById(R.id.popup_item_icon);
}
public void setSwipeDetector(SwipeDetector swipeDetector) {
@@ -90,8 +114,7 @@
/**
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
- public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
- boolean animate) {
+ public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
mNotificationInfo = mainNotification;
CharSequence title = mNotificationInfo.title;
CharSequence text = mNotificationInfo.text;
@@ -103,20 +126,30 @@
mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
mTextView.setVisibility(GONE);
}
- iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mBackgroundColor));
if (mNotificationInfo.intent != null) {
setOnClickListener(mNotificationInfo);
}
- setTranslationX(0);
+ setContentTranslation(0);
// Add a dummy ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
- setTag(new ItemInfo());
+ setTag(NOTIFICATION_ITEM_INFO);
if (animate) {
ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
}
}
+ public void setContentTranslation(float translation) {
+ mTextAndBackground.setTranslationX(translation);
+ mIconView.setTranslationX(translation);
+ }
+
+ public void setContentVisibility(int visibility) {
+ mTextAndBackground.setVisibility(visibility);
+ mIconView.setVisibility(visibility);
+ }
+
public NotificationInfo getNotificationInfo() {
return mNotificationInfo;
}
@@ -143,9 +176,9 @@
@Override
public boolean onDrag(float displacement, float velocity) {
- setTranslationX(canChildBeDismissed()
+ setContentTranslation(canChildBeDismissed()
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
- animate().cancel();
+ mContentTranslateAnimator.cancel();
return true;
}
@@ -153,6 +186,7 @@
public void onDragEnd(float velocity, boolean fling) {
final boolean willExit;
final float endTranslation;
+ final float startTranslation = mTextAndBackground.getTranslationX();
if (!canChildBeDismissed()) {
willExit = false;
@@ -160,28 +194,30 @@
} else if (fling) {
willExit = true;
endTranslation = velocity < 0 ? - getWidth() : getWidth();
- } else if (Math.abs(getTranslationX()) > getWidth() / 2) {
+ } else if (Math.abs(startTranslation) > getWidth() / 2) {
willExit = true;
- endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth());
+ endTranslation = (startTranslation < 0 ? -getWidth() : getWidth());
} else {
willExit = false;
endTranslation = 0;
}
long duration = SwipeDetector.calculateDuration(velocity,
- (endTranslation - getTranslationX()) / getWidth());
- animate()
- .setDuration(duration)
- .setInterpolator(scrollInterpolatorForVelocity(velocity))
- .translationX(endTranslation)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mSwipeDetector.finishedScrolling();
- if (willExit) {
- onChildDismissed();
- }
- }
- }).start();
+ (endTranslation - startTranslation) / getWidth());
+
+ mContentTranslateAnimator.removeAllListeners();
+ mContentTranslateAnimator.setDuration(duration)
+ .setInterpolator(scrollInterpolatorForVelocity(velocity));
+ mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
+ mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mSwipeDetector.finishedScrolling();
+ if (willExit) {
+ onChildDismissed();
+ }
+ }
+ });
+ mContentTranslateAnimator.start();
}
}
diff --git a/src/com/android/launcher3/popup/BaseActionPopup.java b/src/com/android/launcher3/popup/BaseActionPopup.java
deleted file mode 100644
index 7ffe2ef..0000000
--- a/src/com/android/launcher3/popup/BaseActionPopup.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.popup;
-
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-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.res.Resources;
-import android.graphics.CornerPathEffect;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Build;
-import android.support.annotation.IntDef;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
-import com.android.launcher3.logging.LoggerUtils;
-import com.android.launcher3.notification.NotificationItemView;
-import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Themes;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-
-/**
- * Base popup container for showing shortcuts to deep links within apps.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class BaseActionPopup<V extends TextView> extends AbstractFloatingView {
-
- public static final int ROUNDED_TOP_CORNERS = 1 << 0;
- public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
-
- @IntDef(flag = true, value = {
- ROUNDED_TOP_CORNERS,
- ROUNDED_BOTTOM_CORNERS
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RoundedCornerFlags {}
-
- protected final Launcher mLauncher;
- protected final LauncherAccessibilityDelegate mAccessibilityDelegate;
- private final boolean mIsRtl;
-
- public ShortcutsItemView mShortcutsItemView;
-
- protected V mOriginalIcon;
- private final Rect mTempRect = new Rect();
- private PointF mInterceptTouchDown = new PointF();
- private boolean mIsLeftAligned;
- protected boolean mIsAboveIcon;
- protected View mArrow;
- private int mGravity;
-
- protected Animator mOpenCloseAnimator;
- protected boolean mDeferContainerRemoval;
- private final Rect mStartRect = new Rect();
- private final Rect mEndRect = new Rect();
-
- public BaseActionPopup(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
-
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
- mIsRtl = Utilities.isRtl(getResources());
- }
-
- public BaseActionPopup(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BaseActionPopup(Context context) {
- this(context, null, 0);
- }
-
- public LauncherAccessibilityDelegate getAccessibilityDelegate() {
- return mAccessibilityDelegate;
- }
-
- protected PopupItemView getItemViewAt(int index) {
- if (!mIsAboveIcon) {
- // Opening down, so arrow is the first view.
- index++;
- }
- return (PopupItemView) getChildAt(index);
- }
-
- protected int getItemCount() {
- // All children except the arrow are items.
- return getChildCount() - 1;
- }
-
- protected void animateOpen() {
- setVisibility(View.VISIBLE);
- mIsOpen = true;
-
- final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
- final Resources res = getResources();
- final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
- final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
- // Rectangular reveal.
- int itemsTotalHeight = 0;
- for (int i = 0; i < getItemCount(); i++) {
- itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
- }
- Point startPoint = computeAnimStartPoint(itemsTotalHeight);
- int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
- float radius = getItemViewAt(0).getBackgroundRadius();
- mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
- mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
- final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
- (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
- revealAnim.setDuration(revealDuration);
- revealAnim.setInterpolator(revealInterpolator);
-
- Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
- fadeIn.setDuration(revealDuration);
- fadeIn.setInterpolator(revealInterpolator);
- openAnim.play(fadeIn);
-
- // Animate the arrow.
- mArrow.setScaleX(0);
- mArrow.setScaleY(0);
- Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
- R.integer.config_popupArrowOpenDuration));
-
- openAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- Utilities.sendCustomAccessibilityEvent(
- BaseActionPopup.this,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.action_deep_shortcut));
- }
- });
-
- mOpenCloseAnimator = openAnim;
- openAnim.playSequentially(revealAnim, arrowScale);
- openAnim.start();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- enforceContainedWithinScreen(l, r);
- }
-
- private void enforceContainedWithinScreen(int left, int right) {
- DragLayer dragLayer = mLauncher.getDragLayer();
- if (getTranslationX() + left < 0 ||
- getTranslationX() + right > dragLayer.getWidth()) {
- // If we are still off screen, center horizontally too.
- mGravity |= Gravity.CENTER_HORIZONTAL;
- }
-
- if (Gravity.isHorizontal(mGravity)) {
- setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
- }
- if (Gravity.isVertical(mGravity)) {
- setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
- }
- }
-
- /**
- * Returns the point at which the center of the arrow merges with the first popup item.
- */
- private Point computeAnimStartPoint(int itemsTotalHeight) {
- int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
- R.dimen.popup_arrow_horizontal_center_start:
- R.dimen.popup_arrow_horizontal_center_end);
- if (!mIsLeftAligned) {
- arrowCenterX = getMeasuredWidth() - arrowCenterX;
- }
- int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
- - itemsTotalHeight;
- // The y-coordinate of edge between the arrow and the first popup item.
- int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
- return new Point(arrowCenterX, arrowEdge);
- }
-
- /**
- * Orients this container above or below the given icon, aligning with the left or right.
- *
- * These are the preferred orientations, in order (RTL prefers right-aligned over left):
- * - Above and left-aligned
- * - Above and right-aligned
- * - Below and left-aligned
- * - Below and right-aligned
- *
- * So we always align left if there is enough horizontal space
- * and align above if there is enough vertical space.
- */
- protected void orientAboutIcon(int arrowHeight) {
- int width = getMeasuredWidth();
- int height = getMeasuredHeight() + arrowHeight;
-
- DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
- Rect insets = dragLayer.getInsets();
-
- // Align left (right in RTL) if there is room.
- int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
- int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
- int x = leftAlignedX;
- boolean canBeLeftAligned = leftAlignedX + width + insets.left
- < dragLayer.getRight() - insets.right;
- boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
- if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
- x = rightAlignedX;
- }
- mIsLeftAligned = x == leftAlignedX;
- if (mIsRtl) {
- x -= dragLayer.getWidth() - width;
- }
-
- // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
- int iconWidth = mOriginalIcon.getWidth()
- - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
- iconWidth *= mOriginalIcon.getScaleX();
- Resources resources = getResources();
- int xOffset;
- if (isAlignedWithStart()) {
- // Aligning with the shortcut icon.
- int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
- int shortcutPaddingStart = resources.getDimensionPixelSize(
- R.dimen.popup_padding_start);
- xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
- } else {
- // Aligning with the drag handle.
- int shortcutDragHandleWidth = resources.getDimensionPixelSize(
- R.dimen.deep_shortcut_drag_handle_size);
- int shortcutPaddingEnd = resources.getDimensionPixelSize(
- R.dimen.popup_padding_end);
- xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
- }
- x += mIsLeftAligned ? xOffset : -xOffset;
-
- // Open above icon if there is room.
- int iconHeight = getIconHeightForPopupPlacement();
- int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
- mIsAboveIcon = y > dragLayer.getTop() + insets.top;
- if (!mIsAboveIcon) {
- y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight;
- }
-
- // Insets are added later, so subtract them now.
- if (mIsRtl) {
- x += insets.right;
- } else {
- x -= insets.left;
- }
- y -= insets.top;
-
- mGravity = 0;
- if (y + height > dragLayer.getBottom() - insets.bottom) {
- // The container is opening off the screen, so just center it in the drag layer instead.
- mGravity = Gravity.CENTER_VERTICAL;
- // Put the container next to the icon, preferring the right side in ltr (left in rtl).
- int rightSide = leftAlignedX + iconWidth - insets.left;
- int leftSide = rightAlignedX - iconWidth - insets.left;
- if (!mIsRtl) {
- if (rightSide + width < dragLayer.getRight()) {
- x = rightSide;
- mIsLeftAligned = true;
- } else {
- x = leftSide;
- mIsLeftAligned = false;
- }
- } else {
- if (leftSide > dragLayer.getLeft()) {
- x = leftSide;
- mIsLeftAligned = false;
- } else {
- x = rightSide;
- mIsLeftAligned = true;
- }
- }
- mIsAboveIcon = true;
- }
-
- setX(x);
- setY(y);
- }
-
- protected int getIconHeightForPopupPlacement() {
- return mOriginalIcon.getHeight();
- }
-
- protected boolean isAlignedWithStart() {
- return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
- }
-
- /**
- * Adds an arrow view pointing at the original icon.
- * @param horizontalOffset the horizontal offset of the arrow, so that it
- * points at the center of the original icon
- */
- protected View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
- LayoutParams layoutParams = new LayoutParams(width, height);
- if (mIsLeftAligned) {
- layoutParams.gravity = Gravity.LEFT;
- layoutParams.leftMargin = horizontalOffset;
- } else {
- layoutParams.gravity = Gravity.RIGHT;
- layoutParams.rightMargin = horizontalOffset;
- }
- if (mIsAboveIcon) {
- layoutParams.topMargin = verticalOffset;
- } else {
- layoutParams.bottomMargin = verticalOffset;
- }
-
- View arrowView = new View(getContext());
- if (Gravity.isVertical(mGravity)) {
- // This is only true if there wasn't room for the container next to the icon,
- // so we centered it instead. In that case we don't want to show the arrow.
- arrowView.setVisibility(INVISIBLE);
- } else {
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- width, height, !mIsAboveIcon));
- Paint arrowPaint = arrowDrawable.getPaint();
- arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
- // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
- int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
- arrowPaint.setPathEffect(new CornerPathEffect(radius));
- arrowView.setBackground(arrowDrawable);
- arrowView.setElevation(getElevation());
- }
- addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
- return arrowView;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mInterceptTouchDown.set(ev.getX(), ev.getY());
- return false;
- }
- // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
- return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
- > ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- protected ObjectAnimator createArrowScaleAnim(float scale) {
- return LauncherAnimUtils.ofPropertyValuesHolder(
- mArrow, new PropertyListBuilder().scale(scale).build());
- }
-
- @Override
- protected void handleClose(boolean animate) {
- if (animate) {
- animateClose();
- } else {
- closeComplete();
- }
- }
-
- protected void animateClose() {
- if (!mIsOpen) {
- return;
- }
- mEndRect.setEmpty();
- if (mOpenCloseAnimator != null) {
- Outline outline = new Outline();
- getOutlineProvider().getOutline(this, outline);
- outline.getRect(mEndRect);
- mOpenCloseAnimator.cancel();
- }
- mIsOpen = false;
-
- final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
- prepareCloseAnimator(closeAnim);
-
- closeAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator = null;
- if (mDeferContainerRemoval) {
- setVisibility(INVISIBLE);
- } else {
- closeComplete();
- }
- }
- });
- mOpenCloseAnimator = closeAnim;
- closeAnim.start();
- }
-
- protected void prepareCloseAnimator(AnimatorSet closeAnim) {
- final Resources res = getResources();
- final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
- // Rectangular reveal (reversed).
- int itemsTotalHeight = 0;
- for (int i = 0; i < getItemCount(); i++) {
- itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
- }
- Point startPoint = computeAnimStartPoint(itemsTotalHeight);
- int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
- float radius = getItemViewAt(0).getBackgroundRadius();
- mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
- if (mEndRect.isEmpty()) {
- mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
- }
- final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
- radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
- revealAnim.setInterpolator(revealInterpolator);
- closeAnim.play(revealAnim);
-
- Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
- fadeOut.setInterpolator(revealInterpolator);
- closeAnim.play(fadeOut);
- closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
- }
-
- /**
- * Closes the folder without animation.
- */
- protected void closeComplete() {
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel();
- mOpenCloseAnimator = null;
- }
- mIsOpen = false;
- mDeferContainerRemoval = false;
- mLauncher.getDragLayer().removeView(this);
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ACTION_POPUP) != 0;
- }
-
- /**
- * Returns a DeepShortcutsContainer which is already open or null
- */
- public static BaseActionPopup getOpen(Launcher launcher) {
- return getOpenView(launcher, TYPE_ACTION_POPUP);
- }
-
- @Override
- public void logActionCommand(int command) {
- mLauncher.getUserEventDispatcher().logActionCommand(
- command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dl = mLauncher.getDragLayer();
- if (!dl.isEventOverView(this, ev)) {
- mLauncher.getUserEventDispatcher().logActionTapOutside(
- LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
- close(true);
-
- // We let touches on the original icon go through so that users can launch
- // the app with one tap if they don't find a shortcut they want.
- return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
- }
- }
- return false;
- }
-
- public void populateAndShow(V originalIcon, PopupPopulator.Item[] itemsToPopulate) {
- setVisibility(View.INVISIBLE);
- mLauncher.getDragLayer().addView(this);
-
- final Resources resources = getResources();
- final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
- final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
- final int arrowVerticalOffset = resources.getDimensionPixelSize(
- R.dimen.popup_arrow_vertical_offset);
-
- mOriginalIcon = originalIcon;
-
- // Add dummy views first, and populate with real info when ready.
- addDummyViews(itemsToPopulate);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- orientAboutIcon(arrowHeight + arrowVerticalOffset);
-
- boolean reverseOrder = mIsAboveIcon;
- if (reverseOrder) {
- removeAllViews();
- mShortcutsItemView = null;
- itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
- addDummyViews(itemsToPopulate);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- orientAboutIcon(arrowHeight + arrowVerticalOffset);
- }
-
- // Add the arrow.
- final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
- R.dimen.popup_arrow_horizontal_offset_start :
- R.dimen.popup_arrow_horizontal_offset_end);
- mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
- mArrow.setPivotX(arrowWidth / 2);
- mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- animateOpen();
- }
-
- protected void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate) {
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
- int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- int numItems = itemTypesToPopulate.length;
- for (int i = 0; i < numItems; i++) {
- PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
- PopupPopulator.Item prevItemTypeToPopulate =
- i > 0 ? itemTypesToPopulate[i - 1] : null;
- PopupPopulator.Item nextItemTypeToPopulate =
- i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
- final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
-
- boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
- boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
- onViewInflated(item, itemTypeToPopulate,
- shouldUnroundTopCorners, shouldUnroundBottomCorners);
-
- if (itemTypeToPopulate.isShortcut) {
- if (mShortcutsItemView == null) {
- mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
- R.layout.shortcuts_item, this, false);
- addView(mShortcutsItemView);
- if (shouldUnroundTopCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
- }
- }
- mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
- if (shouldUnroundBottomCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- }
- } else {
- addView(item);
- }
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
- }
-
- protected void onViewInflated(View view, PopupPopulator.Item itemType,
- boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
-
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4435afb..3dc58a1 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,26 +16,39 @@
package com.android.launcher3.popup;
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -45,42 +58,103 @@
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.badge.BadgeInfo;
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.graphics.IconPalette;
+import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
-import com.android.launcher3.popup.PopupPopulator.Item;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Themes;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
/**
* A container for shortcuts to deep links and notifications associated with an app.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> implements DragSource,
- DragController.DragListener {
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+ DragController.DragListener, View.OnLongClickListener,
+ View.OnTouchListener {
+
+ private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
+ private final PointF mInterceptTouchDown = new PointF();
+ private final Rect mTempRect = new Rect();
+ private final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
+ private final LayoutInflater mInflater;
+ private final float mOutlineRadius;
+ private final Launcher mLauncher;
+ private final LauncherAccessibilityDelegate mAccessibilityDelegate;
+ private final boolean mIsRtl;
+ private final int mArrayOffset;
+ private final View mArrow;
+
+ private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
- private AnimatorSet mReduceHeightAnimatorSet;
+
+ private ViewGroup mSystemShortcutContainer;
+
+ private boolean mIsLeftAligned;
+ protected boolean mIsAboveIcon;
private int mNumNotifications;
+ private int mGravity;
+
+ protected Animator mOpenCloseAnimator;
+ protected boolean mDeferContainerRemoval;
+ private final Rect mStartRect = new Rect();
+ private final Rect mEndRect = new Rect();
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
+ mInflater = LayoutInflater.from(context);
+ mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+ mLauncher = Launcher.getLauncher(context);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+ mIsRtl = Utilities.isRtl(getResources());
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+ }
+ });
+
+ // Initialize arrow view
+ final Resources resources = getResources();
+ final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+ final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+ mArrow = new View(context);
+ mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
+ mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -91,6 +165,75 @@
this(context, null, 0);
}
+ public LauncherAccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mInterceptTouchDown.set(ev.getX(), ev.getY());
+ }
+ if (mNotificationItemView != null
+ && mNotificationItemView.onInterceptTouchEvent(ev)) {
+ return true;
+ }
+ // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+ return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+ > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mNotificationItemView != null) {
+ return mNotificationItemView.onTouchEvent(ev);
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ACTION_POPUP) != 0;
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ mLauncher.getUserEventDispatcher().logActionCommand(
+ command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ DragLayer dl = mLauncher.getDragLayer();
+ if (!dl.isEventOverView(this, ev)) {
+ mLauncher.getUserEventDispatcher().logActionTapOutside(
+ LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
+ close(true);
+
+ // We let touches on the original icon go through so that users can launch
+ // the app with one tap if they don't find a shortcut they want.
+ return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ public <T extends View> T inflateAndAdd(int resId) {
+ View view = mInflater.inflate(resId, this, false);
+ addView(view);
+ return (T) view;
+ }
+
/**
* Shows the notifications and deep shortcuts associated with {@param icon}.
* @return the container if shown or null.
@@ -124,188 +267,420 @@
private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
- PopupPopulator.Item[] itemsToPopulate = PopupPopulator
- .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
- populateAndShow(originalIcon, itemsToPopulate);
- ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
- List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getDeepShortcutViews(mIsAboveIcon);
- List<View> systemShortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getSystemShortcutViews(mIsAboveIcon);
- if (mNotificationItemView != null) {
+ setVisibility(View.INVISIBLE);
+ mLauncher.getDragLayer().addView(this);
+
+ mOriginalIcon = originalIcon;
+
+ // Add views
+ if (mNumNotifications > 0) {
+ // Add notification entries
+ View.inflate(getContext(), R.layout.notification_content, this);
+ mNotificationItemView = new NotificationItemView(this);
+ if (mNumNotifications == 1) {
+ mNotificationItemView.removeFooter();
+ }
updateNotificationHeader();
}
+ int viewsToFlip = getChildCount();
+ mSystemShortcutContainer = this;
- int numShortcuts = shortcutViews.size() + systemShortcutViews.size();
- int numNotifications = notificationKeys.size();
- if (numNotifications == 0) {
+ if (!shortcutIds.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
+
+ for (int i = shortcutIds.size(); i > 0; i--) {
+ mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut));
+ }
+ updateHiddenShortcuts();
+
+ if (!systemShortcuts.isEmpty()) {
+ mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons);
+ for (SystemShortcut shortcut : systemShortcuts) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, shortcut);
+ }
+ }
+ } else if (!systemShortcuts.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
+
+ for (SystemShortcut shortcut : systemShortcuts) {
+ initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut);
+ }
+ }
+ orientAboutIcon();
+
+ boolean reverseOrder = mIsAboveIcon;
+ if (reverseOrder) {
+ int count = getChildCount();
+ ArrayList<View> allViews = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ if (i == viewsToFlip) {
+ Collections.reverse(allViews);
+ }
+ allViews.add(getChildAt(i));
+ }
+ Collections.reverse(allViews);
+ removeAllViews();
+ for (int i = 0; i < count; i++) {
+ addView(allViews.get(i));
+ }
+ if (mNotificationItemView != null) {
+ mNotificationItemView.inverseGutterMargin();
+ }
+
+ orientAboutIcon();
+ }
+ updateDividers();
+
+ // Add the arrow.
+ final int arrowHorizontalOffset = getResources().getDimensionPixelSize(isAlignedWithStart()
+ ? R.dimen.popup_arrow_horizontal_offset_start
+ : R.dimen.popup_arrow_horizontal_offset_end);
+ mLauncher.getDragLayer().addView(mArrow);
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsLeftAligned) {
+ mArrow.setX(getX() + arrowHorizontalOffset);
+ } else {
+ mArrow.setX(getX() + getMeasuredWidth() - arrowHorizontalOffset);
+ }
+
+ if (Gravity.isVertical(mGravity)) {
+ // This is only true if there wasn't room for the container next to the icon,
+ // so we centered it instead. In that case we don't want to show the arrow.
+ mArrow.setVisibility(INVISIBLE);
+ } else {
+ ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+ arrowLp.width, arrowLp.height, !mIsAboveIcon));
+ Paint arrowPaint = arrowDrawable.getPaint();
+ arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
+ // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+ int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+ arrowPaint.setPathEffect(new CornerPathEffect(radius));
+ mArrow.setBackground(arrowDrawable);
+ mArrow.setElevation(getElevation());
+ }
+
+ mArrow.setPivotX(arrowLp.width / 2);
+ mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
+
+ animateOpen();
+
+ ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+ int numShortcuts = mShortcuts.size() + systemShortcuts.size();
+ if (mNumNotifications == 0) {
setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
numShortcuts, originalIcon.getContentDescription().toString()));
} else {
setContentDescription(getContext().getString(
R.string.shortcuts_menu_with_notifications_description, numShortcuts,
- numNotifications, originalIcon.getContentDescription().toString()));
+ mNumNotifications, originalIcon.getContentDescription().toString()));
}
mLauncher.getDragController().addDragListener(this);
mOriginalIcon.forceHideBadge(true);
+ // All views are added. Animate layout from now on.
+ setLayoutTransition(new LayoutTransition());
+
// Load the shortcuts on a background thread and update the container as it animates.
final Looper workerLooper = LauncherModel.getWorkerLooper();
new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
- this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
- systemShortcuts, systemShortcutViews));
+ this, shortcutIds, mShortcuts, notificationKeys));
}
- @Override
- protected void addDummyViews(Item[] itemTypesToPopulate) {
- mNotificationItemView = null;
- super.addDummyViews(itemTypesToPopulate);
- if (mNumNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
- }
+ protected boolean isAlignedWithStart() {
+ return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
}
- @Override
- protected void onViewInflated(View view, Item itemType,
- boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
- if (itemType == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) view;
- boolean notificationFooterHasIcons = mNumNotifications > 1;
- int footerHeight = getResources().getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- view.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
+ /**
+ * Orients this container above or below the given icon, aligning with the left or right.
+ *
+ * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+ * - Above and left-aligned
+ * - Above and right-aligned
+ * - Below and left-aligned
+ * - Below and right-aligned
+ *
+ * So we always align left if there is enough horizontal space
+ * and align above if there is enough vertical space.
+ */
+ protected void orientAboutIcon() {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int width = getMeasuredWidth();
+ int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
+ + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+ int height = getMeasuredHeight() + extraVerticalSpace;
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
+ Rect insets = dragLayer.getInsets();
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemType == PopupPopulator.Item.SHORTCUT) {
- view.setAccessibilityDelegate(mAccessibilityDelegate);
+ // Align left (right in RTL) if there is room.
+ int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
+ int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
+ int x = leftAlignedX;
+ boolean canBeLeftAligned = leftAlignedX + width + insets.left
+ < dragLayer.getRight() - insets.right;
+ boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+ if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+ x = rightAlignedX;
+ }
+ mIsLeftAligned = x == leftAlignedX;
+ if (mIsRtl) {
+ x -= dragLayer.getWidth() - width;
}
- if (itemType != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON && itemType.isShortcut
- && mNumNotifications > 0) {
- int prevHeight = view.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- view.getLayoutParams().height = getResources().getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (view instanceof DeepShortcutView) {
- float iconScale = (float) view.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) view).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) view).getIconView().setScaleY(iconScale);
- }
+ // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+ int iconWidth = mOriginalIcon.getWidth()
+ - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
+ iconWidth *= mOriginalIcon.getScaleX();
+ Resources resources = getResources();
+ int xOffset;
+ if (isAlignedWithStart()) {
+ // Aligning with the shortcut icon.
+ int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+ int shortcutPaddingStart = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_start);
+ xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+ } else {
+ // Aligning with the drag handle.
+ int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.deep_shortcut_drag_handle_size);
+ int shortcutPaddingEnd = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_end);
+ xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
}
- }
+ x += mIsLeftAligned ? xOffset : -xOffset;
- private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) {
- final Resources res = getResources();
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
+ // Open above icon if there is room.
+ int iconHeight = getIconHeightForPopupPlacement();
+ int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
+ mIsAboveIcon = y > dragLayer.getTop() + insets.top;
+ if (!mIsAboveIcon) {
+ y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight + extraVerticalSpace;
+ }
- int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- int numItems = itemTypesToPopulate.length;
- for (int i = 0; i < numItems; i++) {
- PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
- PopupPopulator.Item prevItemTypeToPopulate =
- i > 0 ? itemTypesToPopulate[i - 1] : null;
- PopupPopulator.Item nextItemTypeToPopulate =
- i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
- final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+ // Insets are added later, so subtract them now.
+ if (mIsRtl) {
+ x += insets.right;
+ } else {
+ x -= insets.left;
+ }
+ y -= insets.top;
- boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
- boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
- if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) item;
- boolean notificationFooterHasIcons = numNotifications > 1;
- int footerHeight = res.getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
-
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
-
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
- item.setAccessibilityDelegate(mAccessibilityDelegate);
- }
-
- if (itemTypeToPopulate.isShortcut) {
- if (mShortcutsItemView == null) {
- mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
- R.layout.shortcuts_item, this, false);
- addView(mShortcutsItemView);
- if (shouldUnroundTopCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
- }
- }
- if (itemTypeToPopulate != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON
- && numNotifications > 0) {
- int prevHeight = item.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- item.getLayoutParams().height = res.getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (item instanceof DeepShortcutView) {
- float iconScale = (float) item.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) item).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) item).getIconView().setScaleY(iconScale);
- }
- }
- mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
- if (shouldUnroundBottomCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
+ mGravity = 0;
+ if (y + height > dragLayer.getBottom() - insets.bottom) {
+ // The container is opening off the screen, so just center it in the drag layer instead.
+ mGravity = Gravity.CENTER_VERTICAL;
+ // Put the container next to the icon, preferring the right side in ltr (left in rtl).
+ int rightSide = leftAlignedX + iconWidth - insets.left;
+ int leftSide = rightAlignedX - iconWidth - insets.left;
+ if (!mIsRtl) {
+ if (rightSide + width < dragLayer.getRight()) {
+ x = rightSide;
+ mIsLeftAligned = true;
+ } else {
+ x = leftSide;
+ mIsLeftAligned = false;
}
} else {
- addView(item);
+ if (leftSide > dragLayer.getLeft()) {
+ x = leftSide;
+ mIsLeftAligned = false;
+ } else {
+ x = rightSide;
+ mIsLeftAligned = true;
+ }
}
+ mIsAboveIcon = true;
}
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
- if (numNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
+
+ setX(x);
+ if (Gravity.isVertical(mGravity)) {
+ return;
+ }
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsAboveIcon) {
+ arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
+ lp.bottomMargin =
+ mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
+ arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
+ } else {
+ arrowLp.gravity = lp.gravity = Gravity.TOP;
+ lp.topMargin = y + insets.top;
+ arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ // enforce contained is within screen
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (getTranslationX() + l < 0 ||
+ getTranslationX() + r > dragLayer.getWidth()) {
+ // If we are still off screen, center horizontally too.
+ mGravity |= Gravity.CENTER_HORIZONTAL;
+ }
+
+ if (Gravity.isHorizontal(mGravity)) {
+ setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+ mArrow.setVisibility(INVISIBLE);
+ }
+ if (Gravity.isVertical(mGravity)) {
+ setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+ }
+ }
+
+ protected void animateOpen() {
+ setVisibility(View.VISIBLE);
+ mIsOpen = true;
+
+ final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+ final Resources res = getResources();
+ final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal.
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, false);
+ revealAnim.setDuration(revealDuration);
+ revealAnim.setInterpolator(revealInterpolator);
+
+ Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+ fadeIn.setDuration(revealDuration);
+ fadeIn.setInterpolator(revealInterpolator);
+ openAnim.play(fadeIn);
+
+ // Animate the arrow.
+ mArrow.setScaleX(0);
+ mArrow.setScaleY(0);
+ Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
+ .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
+
+ openAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ Utilities.sendCustomAccessibilityEvent(
+ PopupContainerWithArrow.this,
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.action_deep_shortcut));
+ }
+ });
+
+ mOpenCloseAnimator = openAnim;
+ openAnim.playSequentially(revealAnim, arrowScale);
+ openAnim.start();
+ }
+
+ public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
+ mNotificationItemView.applyNotificationInfos(notificationInfos);
+ }
+
+ private void updateHiddenShortcuts() {
+ int allowedCount = mNotificationItemView != null
+ ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
+ int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
+ int itemHeight = mNotificationItemView != null ?
+ getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
+ : originalHeight;
+ float iconScale = ((float) itemHeight) / originalHeight;
+
+ int total = mShortcuts.size();
+ for (int i = 0; i < total; i++) {
+ DeepShortcutView view = mShortcuts.get(i);
+ view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
+ view.getLayoutParams().height = itemHeight;
+ view.getIconView().setScaleX(iconScale);
+ view.getIconView().setScaleY(iconScale);
+ }
+ }
+
+ private void updateDividers() {
+ int count = getChildCount();
+ DeepShortcutView lastView = null;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+ if (lastView != null) {
+ lastView.setDividerVisibility(VISIBLE);
+ }
+ lastView = (DeepShortcutView) view;
+ lastView.setDividerVisibility(INVISIBLE);
+ }
}
}
@Override
protected void onWidgetsBound() {
- if (mShortcutsItemView != null) {
- mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon);
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+ View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (onClickListener != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != this) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need to
+ // reopen the container to ensure measurements etc. all work out. While this could
+ // be quite janky, in practice the user would typically see a small flicker as the
+ // animation restarts partway through, and this is a very rare edge case anyway.
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (onClickListener == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
}
}
- @Override
+ private void initializeSystemShortcut(View view, SystemShortcut info) {
+ if (view instanceof DeepShortcutView) {
+ // Expanded system shortcut, with both icon and text shown on white background.
+ final DeepShortcutView shortcutView = (DeepShortcutView) view;
+ shortcutView.getIconView().setBackgroundResource(info.iconResId);
+ shortcutView.getBubbleText().setText(info.labelResId);
+ } else if (view instanceof ImageView) {
+ // Only the system shortcut icon shows on a gray background header.
+ final ImageView shortcutIcon = (ImageView) view;
+ shortcutIcon.setImageResource(info.iconResId);
+ shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
+ }
+ view.setTag(info);
+ view.setOnClickListener(info.getOnClickListener(mLauncher,
+ (ItemInfo) mOriginalIcon.getTag()));
+ }
+
protected int getIconHeightForPopupPlacement() {
return mOriginalIcon.getIcon() != null
? mOriginalIcon.getIcon().getBounds().height()
@@ -383,108 +758,15 @@
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
- // There are no more notifications, so create an animation to remove
- // the notifications view and expand the shortcuts view (if possible).
- AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
- int hiddenShortcutsHeight = 0;
- if (mShortcutsItemView != null) {
- hiddenShortcutsHeight = mShortcutsItemView.getHiddenShortcutsHeight();
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- // With notifications gone, all corners of shortcuts item should be rounded.
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
- ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
- removeNotification.play(mShortcutsItemView.showAllShortcuts(mIsAboveIcon));
- }
- final int duration = getResources().getInteger(
- R.integer.config_removeNotificationViewDuration);
- removeNotification.play(adjustItemHeights(mNotificationItemView.getHeightMinusFooter(),
- hiddenShortcutsHeight, duration));
- Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
- .setDuration(duration);
- fade.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- removeView(mNotificationItemView);
- mNotificationItemView = null;
- if (getItemCount() == 0) {
- close(false);
- }
- }
- });
- removeNotification.play(fade);
- final long arrowScaleDuration = getResources().getInteger(
- R.integer.config_popupArrowOpenDuration);
- Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
- hideArrow.setStartDelay(0);
- Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
- showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
- removeNotification.playSequentially(hideArrow, showArrow);
- removeNotification.start();
- return;
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(badgeInfo.getNotificationKeys()));
}
- mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
- badgeInfo.getNotificationKeys()));
- }
-
- public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
- return adjustItemHeights(heightToRemove, 0, duration);
- }
-
- /**
- * Animates the height of the notification item and the translationY of other items accordingly.
- */
- public Animator adjustItemHeights(int notificationHeightToRemove, int shortcutHeightToAdd,
- int duration) {
- if (mReduceHeightAnimatorSet != null) {
- mReduceHeightAnimatorSet.cancel();
- }
- final int translateYBy = mIsAboveIcon ? notificationHeightToRemove - shortcutHeightToAdd
- : -notificationHeightToRemove;
- mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
- boolean removingNotification =
- notificationHeightToRemove == mNotificationItemView.getHeightMinusFooter();
- boolean shouldRemoveNotificationHeightFromTop = mIsAboveIcon && removingNotification;
- mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(
- notificationHeightToRemove, shouldRemoveNotificationHeightFromTop));
- PropertyResetListener<View, Float> resetTranslationYListener
- = new PropertyResetListener<>(TRANSLATION_Y, 0f);
- boolean itemIsAfterShortcuts = false;
- for (int i = 0; i < getItemCount(); i++) {
- final PopupItemView itemView = getItemViewAt(i);
- if (itemIsAfterShortcuts) {
- // Every item after the shortcuts item needs to adjust for the new height.
- itemView.setTranslationY(itemView.getTranslationY() - shortcutHeightToAdd);
- }
- if (itemView == mNotificationItemView && (!mIsAboveIcon || removingNotification)) {
- // The notification view is already in the right place.
- continue;
- }
- ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
- itemView.getTranslationY() + translateYBy).setDuration(duration);
- translateItem.addListener(resetTranslationYListener);
- mReduceHeightAnimatorSet.play(translateItem);
- if (itemView == mShortcutsItemView) {
- itemIsAfterShortcuts = true;
- }
- }
- if (mIsAboveIcon) {
- // We also need to adjust the arrow position to account for the new shortcuts height.
- mArrow.setTranslationY(mArrow.getTranslationY() - shortcutHeightToAdd);
- }
- mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mIsAboveIcon) {
- // All the items, including the notification item, translated down, but the
- // container itself did not. This means the items would jump back to their
- // original translation unless we update the container's translationY here.
- setTranslationY(getTranslationY() + translateYBy);
- mArrow.setTranslationY(0);
- }
- mReduceHeightAnimatorSet = null;
- }
- });
- return mReduceHeightAnimatorSet;
}
@Override
@@ -515,25 +797,146 @@
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.itemType = ItemType.DEEPSHORTCUT;
+ if (info == NOTIFICATION_ITEM_INFO) {
+ target.itemType = ItemType.NOTIFICATION;
+ } else {
+ target.itemType = ItemType.DEEPSHORTCUT;
+ target.rank = info.rank;
+ }
targetParent.containerType = ContainerType.DEEPSHORTCUTS;
}
- @Override
- protected void prepareCloseAnimator(AnimatorSet closeAnim) {
+ protected void animateClose() {
+ if (!mIsOpen) {
+ return;
+ }
+ mEndRect.setEmpty();
+ if (mOpenCloseAnimator != null) {
+ Outline outline = new Outline();
+ getOutlineProvider().getOutline(this, outline);
+ outline.getRect(mEndRect);
+ mOpenCloseAnimator.cancel();
+ }
+ mIsOpen = false;
+
+ final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+ // Hide the arrow
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
+
// Animate original icon's text back in.
closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
-
mOriginalIcon.forceHideBadge(false);
- super.prepareCloseAnimator(closeAnim);
+
+ final Resources res = getResources();
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal (reversed).
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, true);
+ revealAnim.setInterpolator(revealInterpolator);
+ closeAnim.play(revealAnim);
+
+ Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+ fadeOut.setInterpolator(revealInterpolator);
+ closeAnim.play(fadeOut);
+ closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
+
+ closeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ if (mDeferContainerRemoval) {
+ setVisibility(INVISIBLE);
+ } else {
+ closeComplete();
+ }
+ }
+ });
+ mOpenCloseAnimator = closeAnim;
+ closeAnim.start();
}
- @Override
- protected void closeComplete() {
+ private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+ int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
+ R.dimen.popup_arrow_horizontal_center_start:
+ R.dimen.popup_arrow_horizontal_center_end);
+ if (!mIsLeftAligned) {
+ arrowCenterX = getMeasuredWidth() - arrowCenterX;
+ }
+ int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
+
+ mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
+ if (mEndRect.isEmpty()) {
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ return new RoundedRectRevealOutlineProvider
+ (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
+ }
+
+ /**
+ * Closes the popup without animation.
+ */
+ private void closeComplete() {
mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
mOriginalIcon.forceHideBadge(false);
mLauncher.getDragController().removeDragListener(this);
- super.closeComplete();
+ if (mOpenCloseAnimator != null) {
+ mOpenCloseAnimator.cancel();
+ mOpenCloseAnimator = null;
+ }
+ mIsOpen = false;
+ mDeferContainerRemoval = false;
+ mLauncher.getDragLayer().removeView(this);
+ mLauncher.getDragLayer().removeView(mArrow);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // 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();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ this, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+
+ /**
+ * Returns a PopupContainerWithArrow which is already open or null
+ */
+ public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_ACTION_POPUP);
}
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 070ac39..abc186b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -148,9 +148,9 @@
}
private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
- BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher);
- if (openContainer instanceof PopupContainerWithArrow) {
- ((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges);
+ PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+ if (openContainer != null) {
+ openContainer.trimNotifications(updatedBadges);
}
}
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
deleted file mode 100644
index 75c3f26..0000000
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.popup;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.popup.BaseActionPopup.RoundedCornerFlags;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
-/**
- * An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
- */
-public abstract class PopupItemView extends FrameLayout {
-
- protected final Rect mPillRect;
- protected @RoundedCornerFlags int mRoundedCorners;
- protected final boolean mIsRtl;
- protected View mIconView;
-
- private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- private final Matrix mMatrix = new Matrix();
- private Bitmap mRoundedCornerBitmap;
-
- public PopupItemView(Context context) {
- this(context, null, 0);
- }
-
- public PopupItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mPillRect = new Rect();
-
- // Initialize corner clipping Bitmap and Paint.
- int radius = (int) getBackgroundRadius();
- mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
- Canvas canvas = new Canvas();
- canvas.setBitmap(mRoundedCornerBitmap);
- canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
- mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
-
- mIsRtl = Utilities.isRtl(getResources());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconView = findViewById(R.id.popup_item_icon);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (mRoundedCorners == 0) {
- super.dispatchDraw(canvas);
- return;
- }
-
- int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
- super.dispatchDraw(canvas);
-
- // Clip children to this item's rounded corners.
- int cornerWidth = mRoundedCornerBitmap.getWidth();
- int cornerHeight = mRoundedCornerBitmap.getHeight();
- int cornerCenterX = Math.round(cornerWidth / 2f);
- int cornerCenterY = Math.round(cornerHeight / 2f);
- if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
- // Clip top left corner.
- mMatrix.reset();
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- // Clip top right corner.
- mMatrix.setRotate(90, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- }
- if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
- // Clip bottom right corner.
- mMatrix.setRotate(180, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- // Clip bottom left corner.
- mMatrix.setRotate(270, cornerCenterX, cornerCenterY);
- mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
- canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
- }
-
- canvas.restoreToCount(saveCount);
- }
-
- /**
- * Creates a round rect drawable (with the specified corners unrounded)
- * and sets it as this View's background.
- */
- public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
- mRoundedCorners = roundedCorners;
- float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
- float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
- float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
- ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
- roundRectBackground.getPaint().setColor(color);
- setBackground(roundRectBackground);
- }
-
- protected float getBackgroundRadius() {
- return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
- }
-}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 0dc1ca0..6c83d12 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -17,23 +17,17 @@
package com.android.launcher3.popup;
import android.content.ComponentName;
-import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
-import android.view.View;
-import android.widget.ImageView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.notification.NotificationInfo;
-import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -56,55 +50,6 @@
@VisibleForTesting static final int NUM_DYNAMIC = 2;
public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
- public enum Item {
- SHORTCUT(R.layout.deep_shortcut, true),
- NOTIFICATION(R.layout.notification, false),
- SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
- SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
-
- public final int layoutId;
- public final boolean isShortcut;
-
- Item(int layoutId, boolean isShortcut) {
- this.layoutId = layoutId;
- this.isShortcut = isShortcut;
- }
- }
-
- public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
- @NonNull List<NotificationKeyData> notificationKeys,
- @NonNull List<SystemShortcut> systemShortcuts) {
- boolean hasNotifications = notificationKeys.size() > 0;
- int numNotificationItems = hasNotifications ? 1 : 0;
- int numShortcuts = shortcutIds.size();
- int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
- + systemShortcuts.size();
- Item[] items = new Item[numItems];
- for (int i = 0; i < numItems; i++) {
- items[i] = Item.SHORTCUT;
- }
- if (hasNotifications) {
- // The notification layout is always first.
- items[0] = Item.NOTIFICATION;
- }
- // The system shortcuts are always last.
- boolean iconsOnly = !shortcutIds.isEmpty();
- for (int i = 0; i < systemShortcuts.size(); i++) {
- items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
- }
- return items;
- }
-
- public static Item[] reverseItems(Item[] items) {
- if (items == null) return null;
- int numItems = items.length;
- Item[] reversedArray = new Item[numItems];
- for (int i = 0; i < numItems; i++) {
- reversedArray[i] = items[numItems - i - 1];
- }
- return reversedArray;
- }
-
/**
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
*/
@@ -179,137 +124,42 @@
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
- final List<NotificationKeyData> notificationKeys,
- final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
- final List<View> systemShortcutViews) {
+ final List<NotificationKeyData> notificationKeys) {
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
- return new Runnable() {
- @Override
- public void run() {
- if (notificationView != null) {
- List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
- .getStatusBarNotificationsForKeys(notificationKeys);
- List<NotificationInfo> infos = new ArrayList<>(notifications.size());
- for (int i = 0; i < notifications.size(); i++) {
- StatusBarNotification notification = notifications.get(i);
- infos.add(new NotificationInfo(launcher, notification));
- }
- uiHandler.post(new UpdateNotificationChild(notificationView, infos));
+ return () -> {
+ if (!notificationKeys.isEmpty()) {
+ List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
+ .getStatusBarNotificationsForKeys(notificationKeys);
+ List<NotificationInfo> infos = new ArrayList<>(notifications.size());
+ for (int i = 0; i < notifications.size(); i++) {
+ StatusBarNotification notification = notifications.get(i);
+ infos.add(new NotificationInfo(launcher, notification));
}
-
- List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
- .queryForShortcutsContainer(activity, shortcutIds, user);
- String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
- : notificationKeys.get(0).shortcutId;
- shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
- for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
- final ShortcutInfoCompat shortcut = shortcuts.get(i);
- ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
- // Use unbadged icon for the menu.
- si.iconBitmap = LauncherIcons.createShortcutIcon(
- shortcut, launcher, false /* badged */);
- si.rank = i;
- uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
- si, shortcut));
- }
-
- // This ensures that mLauncher.getWidgetsForPackageUser()
- // doesn't return null (it puts all the widgets in memory).
- for (int i = 0; i < systemShortcuts.size(); i++) {
- final SystemShortcut systemShortcut = systemShortcuts.get(i);
- uiHandler.post(new UpdateSystemShortcutChild(container,
- systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
- }
- uiHandler.post(new Runnable() {
- @Override
- public void run() {
- launcher.refreshAndBindWidgetsForPackageUser(
- PackageUserKey.fromItemInfo(originalInfo));
- }
- });
+ uiHandler.post(() -> container.applyNotificationInfos(infos));
}
+
+ List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+ .queryForShortcutsContainer(activity, shortcutIds, user);
+ String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+ : notificationKeys.get(0).shortcutId;
+ shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+ for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
+ final ShortcutInfoCompat shortcut = shortcuts.get(i);
+ final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
+ // Use unbadged icon for the menu.
+ si.iconBitmap = LauncherIcons.createShortcutIcon(
+ shortcut, launcher, false /* badged */);
+ si.rank = i;
+
+ final DeepShortcutView view = shortcutViews.get(i);
+ uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
+ }
+
+ // This ensures that mLauncher.getWidgetsForPackageUser()
+ // doesn't return null (it puts all the widgets in memory).
+ uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
+ PackageUserKey.fromItemInfo(originalInfo)));
};
}
-
- /** Updates the shortcut child of this container based on the given shortcut info. */
- private static class UpdateShortcutChild implements Runnable {
- private final PopupContainerWithArrow mContainer;
- private final DeepShortcutView mShortcutChild;
- private final ShortcutInfo mShortcutChildInfo;
- private final ShortcutInfoCompat mDetail;
-
- public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
- ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
- mContainer = container;
- mShortcutChild = shortcutChild;
- mShortcutChildInfo = shortcutChildInfo;
- mDetail = detail;
- }
-
- @Override
- public void run() {
- mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
- mContainer.mShortcutsItemView);
- }
- }
-
- /** Updates the notification child based on the given notification info. */
- private static class UpdateNotificationChild implements Runnable {
- private NotificationItemView mNotificationView;
- private List<NotificationInfo> mNotificationInfos;
-
- public UpdateNotificationChild(NotificationItemView notificationView,
- List<NotificationInfo> notificationInfos) {
- mNotificationView = notificationView;
- mNotificationInfos = notificationInfos;
- }
-
- @Override
- public void run() {
- mNotificationView.applyNotificationInfos(mNotificationInfos);
- }
- }
-
- /** Updates the system shortcut child based on the given shortcut info. */
- private static class UpdateSystemShortcutChild implements Runnable {
-
- private final PopupContainerWithArrow mContainer;
- private final View mSystemShortcutChild;
- private final SystemShortcut mSystemShortcutInfo;
- private final Launcher mLauncher;
- private final ItemInfo mItemInfo;
-
- public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
- SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
- mContainer = container;
- mSystemShortcutChild = systemShortcutChild;
- mSystemShortcutInfo = systemShortcut;
- mLauncher = launcher;
- mItemInfo = originalInfo;
- }
-
- @Override
- public void run() {
- final Context context = mSystemShortcutChild.getContext();
- initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
- mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
- .getOnClickListener(mLauncher, mItemInfo));
- }
- }
-
- public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
- if (view instanceof DeepShortcutView) {
- // Expanded system shortcut, with both icon and text shown on white background.
- final DeepShortcutView shortcutView = (DeepShortcutView) view;
- shortcutView.getIconView().setBackground(info.getIcon(context));
- shortcutView.getBubbleText().setText(info.getLabel(context));
- } else if (view instanceof ImageView) {
- // Only the system shortcut icon shows on a gray background header.
- final ImageView shortcutIcon = (ImageView) view;
- shortcutIcon.setImageDrawable(info.getIcon(context));
- shortcutIcon.setContentDescription(info.getLabel(context));
- }
- view.setTag(info);
- }
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index e709b93..c398aaa 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,9 +1,7 @@
package com.android.launcher3.popup;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
@@ -31,20 +29,12 @@
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
*/
public abstract class SystemShortcut extends ItemInfo {
- private final int mIconResId;
- private final int mLabelResId;
+ public final int iconResId;
+ public final int labelResId;
public SystemShortcut(int iconResId, int labelResId) {
- mIconResId = iconResId;
- mLabelResId = labelResId;
- }
-
- public Drawable getIcon(Context context) {
- return context.getResources().getDrawable(mIconResId, context.getTheme());
- }
-
- public String getLabel(Context context) {
- return context.getString(mLabelResId);
+ this.iconResId = iconResId;
+ this.labelResId = labelResId;
}
public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 75a4886..450a690 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -29,6 +29,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.popup.PopupContainerWithArrow;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -42,6 +43,7 @@
private BubbleTextView mBubbleText;
private View mIconView;
+ private View mDivider;
private ShortcutInfo mInfo;
private ShortcutInfoCompat mDetail;
@@ -65,6 +67,11 @@
super.onFinishInflate();
mBubbleText = findViewById(R.id.bubble_text);
mIconView = findViewById(R.id.icon);
+ mDivider = findViewById(R.id.divider);
+ }
+
+ public void setDividerVisibility(int visibility) {
+ mDivider.setVisibility(visibility);
}
public BubbleTextView getBubbleText() {
@@ -98,7 +105,7 @@
/** package private **/
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
- ShortcutsItemView container) {
+ PopupContainerWithArrow container) {
mInfo = info;
mDetail = detail;
mBubbleText.applyFromShortcutInfo(info);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
deleted file mode 100644
index b4fa04e..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.popup.PopupItemView;
-import com.android.launcher3.popup.PopupPopulator;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
- * as well as the system shortcuts such as Widgets and App Info.
- */
-public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
- View.OnTouchListener, LogContainerProvider {
-
- private static final String TAG = "ShortcutsItem";
-
- private Launcher mLauncher;
- private LinearLayout mContent;
- private LinearLayout mShortcutsLayout;
- private LinearLayout mSystemShortcutIcons;
- private final Point mIconShift = new Point();
- private final Point mIconLastTouchPos = new Point();
- private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
- private final List<View> mSystemShortcutViews = new ArrayList<>();
-
- private int mHiddenShortcutsHeight;
-
- public ShortcutsItemView(Context context) {
- this(context, null, 0);
- }
-
- public ShortcutsItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mLauncher = Launcher.getLauncher(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContent = findViewById(R.id.content);
- mShortcutsLayout = findViewById(R.id.shortcuts);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- @Override
- public boolean onLongClick(View v) {
- // 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();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
- (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
- dv.animateShift(-mIconShift.x, -mIconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
- }
-
- public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
- addShortcutView(shortcutView, shortcutType, -1);
- }
-
- private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
- if (shortcutType == PopupPopulator.Item.SHORTCUT) {
- mDeepShortcutViews.add((DeepShortcutView) shortcutView);
- } else {
- mSystemShortcutViews.add(shortcutView);
- }
- if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- // System shortcut icons are added to a header that is separate from the full shortcuts.
- if (mSystemShortcutIcons == null) {
- mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
- R.layout.system_shortcut_icons, mContent, false);
- boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
- mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
- }
- mSystemShortcutIcons.addView(shortcutView, index);
- } else {
- if (mShortcutsLayout.getChildCount() > 0) {
- View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
- if (prevChild instanceof DeepShortcutView) {
- prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
- }
- mShortcutsLayout.addView(shortcutView, index);
- }
- }
-
- public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
- if (reverseOrder) {
- Collections.reverse(mDeepShortcutViews);
- }
- return mDeepShortcutViews;
- }
-
- public List<View> getSystemShortcutViews(boolean reverseOrder) {
- // Always reverse system shortcut icons (in the header)
- // so they are in priority order from right to left.
- if (reverseOrder || mSystemShortcutIcons != null) {
- Collections.reverse(mSystemShortcutViews);
- }
- return mSystemShortcutViews;
- }
-
- /**
- * Hides shortcuts until only {@param maxShortcuts} are showing. Also sets
- * {@link #mHiddenShortcutsHeight} to be the amount of extra space that shortcuts will
- * require when {@link #showAllShortcuts(boolean)} is called.
- */
- public void hideShortcuts(boolean hideFromTop, int maxShortcuts) {
- // When shortcuts are shown, they get more space allocated to them.
- final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
- final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- mHiddenShortcutsHeight = (newHeight - oldHeight) * mShortcutsLayout.getChildCount();
-
- int numToHide = mShortcutsLayout.getChildCount() - maxShortcuts;
- if (numToHide <= 0) {
- return;
- }
- final int numShortcuts = mShortcutsLayout.getChildCount();
- final int dir = hideFromTop ? 1 : -1;
- for (int i = hideFromTop ? 0 : numShortcuts - 1; 0 <= i && i < numShortcuts; i += dir) {
- View child = mShortcutsLayout.getChildAt(i);
- if (child instanceof DeepShortcutView) {
- mHiddenShortcutsHeight += child.getLayoutParams().height;
- child.setVisibility(GONE);
- int prev = i + dir;
- if (!hideFromTop && 0 <= prev && prev < numShortcuts) {
- // When hiding views from the bottom, make sure to hide the last divider.
- mShortcutsLayout.getChildAt(prev).findViewById(R.id.divider).setVisibility(GONE);
- }
- numToHide--;
- if (numToHide == 0) {
- break;
- }
- }
- }
- }
-
- public int getHiddenShortcutsHeight() {
- return mHiddenShortcutsHeight;
- }
-
- /**
- * Sets all shortcuts in {@link #mShortcutsLayout} to VISIBLE, then creates an
- * animation to reveal the newly shown shortcuts.
- *
- * @see #hideShortcuts(boolean, int)
- */
- public Animator showAllShortcuts(boolean showFromTop) {
- // First set all the shortcuts to VISIBLE.
- final int numShortcuts = mShortcutsLayout.getChildCount();
- if (numShortcuts == 0) {
- Log.w(TAG, "Tried to show all shortcuts but there were no shortcuts to show");
- return null;
- }
- final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
- final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- for (int i = 0; i < numShortcuts; i++) {
- DeepShortcutView view = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
- view.getLayoutParams().height = newHeight;
- view.requestLayout();
- view.setVisibility(VISIBLE);
- if (i < numShortcuts - 1) {
- view.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
- }
-
- // Now reveal the newly shown shortcuts.
- AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
- if (showFromTop) {
- // The new shortcuts pushed the original shortcuts down, but we want to animate them
- // to that position. So we revert the translation and animate to the new.
- animation.play(translateYFrom(mShortcutsLayout, -mHiddenShortcutsHeight));
- } else if (mSystemShortcutIcons != null) {
- // When adding the shortcuts from the bottom, things are a little trickier, since
- // that means they push the icons header down. To account for this, we do the same
- // translation trick as above, but on the header. Since this means leaving behind
- // a blank area where the header was, we also need to clip the background.
- animation.play(translateYFrom(mSystemShortcutIcons, -mHiddenShortcutsHeight));
- // mPillRect is the bounds of this view before the new shortcuts were shown.
- Rect backgroundStartRect = new Rect(mPillRect);
- Rect backgroundEndRect = new Rect(mPillRect);
- backgroundEndRect.bottom += mHiddenShortcutsHeight;
- animation.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(),
- getBackgroundRadius(), backgroundStartRect, backgroundEndRect, mRoundedCorners)
- .createRevealAnimator(this, false));
- }
- for (int i = 0; i < numShortcuts; i++) {
- // Animate each shortcut to its new height.
- DeepShortcutView shortcut = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
- int heightDiff = newHeight - oldHeight;
- int heightAdjustmentIndex = showFromTop ? numShortcuts - i - 1 : i;
- int fromDir = showFromTop ? 1 : -1;
- animation.play(translateYFrom(shortcut, heightDiff * heightAdjustmentIndex * fromDir));
- // Make sure the text and icon stay centered in the shortcut.
- animation.play(translateYFrom(shortcut.getBubbleText(), heightDiff / 2 * fromDir));
- animation.play(translateYFrom(shortcut.getIconView(), heightDiff / 2 * fromDir));
- // Scale icons back up to full size.
- animation.play(LauncherAnimUtils.ofPropertyValuesHolder(shortcut.getIconView(),
- new PropertyListBuilder().scale(1f).build()));
- }
- return animation;
- }
-
- /**
- * Animates the translationY of the view from the given offset to the view's current translation
- * @return an Animator, which should be started by the caller.
- */
- private Animator translateYFrom(View v, int diff) {
- float finalY = v.getTranslationY();
- return ObjectAnimator.ofFloat(v, TRANSLATION_Y, finalY + diff, finalY);
- }
-
- /**
- * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
- */
- public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
- ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
- SystemShortcut widgetInfo = new SystemShortcut.Widgets();
- View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
- View widgetsView = null;
- for (View systemShortcutView : mSystemShortcutViews) {
- if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
- widgetsView = systemShortcutView;
- break;
- }
- }
- final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
- ? PopupPopulator.Item.SYSTEM_SHORTCUT
- : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
- if (onClickListener != null && widgetsView == null) {
- // We didn't have any widgets cached but now there are some, so enable the shortcut.
- widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
- PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
- widgetsView.setOnClickListener(onClickListener);
- if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- addShortcutView(widgetsView, widgetsItem, 0);
- } else {
- // If using the expanded system shortcut (as opposed to just the icon), we need to
- // reopen the container to ensure measurements etc. all work out. While this could
- // be quite janky, in practice the user would typically see a small flicker as the
- // animation restarts partway through, and this is a very rare edge case anyway.
- ((PopupContainerWithArrow) getParent()).close(false);
- PopupContainerWithArrow.showForIcon(originalIcon);
- }
- } else if (onClickListener == null && widgetsView != null) {
- // No widgets exist, but we previously added the shortcut so remove it.
- if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
- mSystemShortcutViews.remove(widgetsView);
- mSystemShortcutIcons.removeView(widgetsView);
- } else {
- ((PopupContainerWithArrow) getParent()).close(false);
- PopupContainerWithArrow.showForIcon(originalIcon);
- }
- }
- }
-
- @Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
- target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
- target.rank = info.rank;
- targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
- }
-}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index c6feefc..f084fd2 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -17,42 +17,52 @@
import android.content.Intent;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import com.android.launcher3.Launcher;
import com.android.launcher3.Launcher.OnResumeCallback;
/**
- * Utility class to sending state handling logic to Launcher from within the same process
+ * Utility class to sending state handling logic to Launcher from within the same process.
+ *
+ * Extending {@link Binder} ensures that the platform maintains a single instance of each object
+ * which allows this object to safely navigate the system process.
*/
public abstract class InternalStateHandler extends Binder implements OnResumeCallback {
public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
- public abstract void onCreate(Launcher launcher);
- public abstract void onNewIntent(Launcher launcher, boolean alreadyOnHome);
+ protected abstract void init(Launcher launcher, boolean alreadyOnHome);
- public static void handleCreate(Launcher launcher, Intent intent) {
- if (intent.getExtras() != null) {
- IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
- if (stateBinder instanceof InternalStateHandler) {
- InternalStateHandler handler = (InternalStateHandler) stateBinder;
- launcher.setOnResumeCallback(handler);
- handler.onCreate(launcher);
- }
- intent.getExtras().remove(EXTRA_STATE_HANDLER);
- }
+ public final Intent addToIntent(Intent intent) {
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_STATE_HANDLER, this);
+ intent.putExtras(extras);
+ return intent;
}
- public static void handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
- if (intent.getExtras() != null) {
+ public static boolean handleCreate(Launcher launcher, Intent intent) {
+ return handleIntent(launcher, intent, false);
+ }
+
+ public static boolean handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
+ return handleIntent(launcher, intent, alreadyOnHome);
+ }
+
+ private static boolean handleIntent(
+ Launcher launcher, Intent intent, boolean alreadyOnHome) {
+ boolean result = false;
+ if (intent != null && intent.getExtras() != null) {
IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
if (stateBinder instanceof InternalStateHandler) {
InternalStateHandler handler = (InternalStateHandler) stateBinder;
launcher.setOnResumeCallback(handler);
- handler.onNewIntent(launcher, alreadyOnHome);
+ handler.init(launcher, alreadyOnHome);
+ result = true;
}
intent.getExtras().remove(EXTRA_STATE_HANDLER);
}
+ return result;
}
}
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index 5b66fcd..0f3ac57 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -33,9 +33,8 @@
private static final boolean ENABLED = FeatureFlags.IS_DOGFOOD_BUILD;
- private static final boolean SYSTEM_TRACE = true;
- private static final ArrayMap<String, MutableLong> sUpTimes =
- ENABLED ? new ArrayMap<String, MutableLong>() : null;
+ private static final boolean SYSTEM_TRACE = false;
+ private static final ArrayMap<String, MutableLong> sUpTimes = ENABLED ? new ArrayMap<>() : null;
public static void beginSection(String sectionName) {
if (ENABLED) {
diff --git a/src/com/android/launcher3/views/SlidingTabStrip.java b/src/com/android/launcher3/views/SlidingTabStrip.java
new file mode 100644
index 0000000..45c6261
--- /dev/null
+++ b/src/com/android/launcher3/views/SlidingTabStrip.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class SlidingTabStrip extends LinearLayout {
+
+ private final Paint mSelectedIndicatorPaint;
+ private int mSelectedIndicatorHeight;
+ private int mIndicatorLeft = -1;
+ private int mIndicatorRight = -1;
+ private int mSelectedPosition = -1;
+ private float mSelectionOffset;
+
+ public SlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setOrientation(HORIZONTAL);
+ setWillNotDraw(false);
+ mSelectedIndicatorPaint = new Paint();
+ mSelectedIndicatorPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorAccent));
+ mSelectedIndicatorHeight = getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_tabs_indicator_height);
+ }
+
+ public void updateIndicatorPosition(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ updateIndicatorPosition();
+ }
+
+ public void updateTabTextColor(int pos) {
+ for (int i=0; i < getChildCount(); i++) {
+ Button tab = (Button) getChildAt(i);
+ tab.setSelected(i == pos);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTabTextColor(0);
+ updateIndicatorPosition(0, 0);
+ }
+
+ private void updateIndicatorPosition() {
+ final View tab = getChildAt(mSelectedPosition);
+ int left, right;
+
+ if (tab != null && tab.getWidth() > 0) {
+ left = tab.getLeft();
+ right = tab.getRight();
+
+ if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+ } else {
+ left = right = -1;
+ }
+
+ setIndicatorPosition(left, right);
+ }
+
+ private void setIndicatorPosition(int left, int right) {
+ if (left != mIndicatorLeft || right != mIndicatorRight) {
+ mIndicatorLeft = left;
+ mIndicatorRight = right;
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
+ mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+ }
+}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index c339634..dcf7453 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -34,7 +34,7 @@
public class OverviewState extends LauncherState {
// The percent to shrink the workspace during overview mode
- public static final float SCALE_FACTOR = 0.7f;
+ private static final float SCALE_FACTOR = 0.7f;
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 8521334..51cf661 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,6 +16,8 @@
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.Launcher;
@@ -39,4 +41,8 @@
(OverviewPanel) launcher.getOverviewPanel(),
launcher.getAllAppsController(), launcher.getWorkspace() };
}
+
+ public static void onWorkspaceLongPress(Launcher launcher) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
}