Snap for 7246408 from 07fb2fea34 to sc-v2-release
Change-Id: Icf0bfef63f4a266dd68686bc53807b675bd942b9
This commit is contained in:
@@ -20,25 +20,17 @@ import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBN
|
||||
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.IAssistDataReceiver;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.quickstep.util.AssistContentRequester;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -53,7 +45,6 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
public static final String ACTION_SEARCH = "com.android.quickstep.ACTION_SEARCH";
|
||||
public static final String ELAPSED_NANOS = "niu_actions_elapsed_realtime_nanos";
|
||||
public static final String ACTIONS_URL = "niu_actions_app_url";
|
||||
private static final String ASSIST_KEY_CONTENT = "content";
|
||||
private static final String TAG = "TaskOverlayFactoryGo";
|
||||
|
||||
// Empty constructor required for ResourceBasedOverride
|
||||
@@ -72,13 +63,10 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
*/
|
||||
public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
|
||||
private String mNIUPackageName;
|
||||
private int mTaskId;
|
||||
private Bundle mAssistData;
|
||||
private final Handler mMainThreadHandler;
|
||||
private String mWebUrl;
|
||||
|
||||
private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
|
||||
super(taskThumbnailView);
|
||||
mMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,28 +87,23 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
|
||||
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
|
||||
|
||||
mTaskId = task.key.id;
|
||||
AssistDataReceiverImpl receiver = new AssistDataReceiverImpl();
|
||||
receiver.setOverlay(this);
|
||||
|
||||
try {
|
||||
ActivityTaskManager.getService().requestAssistDataForTask(receiver, mTaskId,
|
||||
mApplicationContext.getPackageName());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to request AssistData");
|
||||
}
|
||||
int taskId = task.key.id;
|
||||
AssistContentRequester contentRequester =
|
||||
new AssistContentRequester(mApplicationContext);
|
||||
contentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when AssistDataReceiverImpl receives data from ActivityTaskManagerService's
|
||||
* AssistDataRequester
|
||||
*/
|
||||
public void onAssistDataReceived(Bundle data) {
|
||||
mMainThreadHandler.post(() -> {
|
||||
if (data != null) {
|
||||
mAssistData = data;
|
||||
}
|
||||
});
|
||||
/** Provide Assist Content to the overlay. */
|
||||
@VisibleForTesting
|
||||
public void onAssistContentReceived(AssistContent assistContent) {
|
||||
mWebUrl = assistContent.getWebUri() != null
|
||||
? assistContent.getWebUri().toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
mWebUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,12 +122,8 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
.setPackage(mNIUPackageName)
|
||||
.putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
|
||||
|
||||
if (mAssistData != null) {
|
||||
final AssistContent content = mAssistData.getParcelable(ASSIST_KEY_CONTENT);
|
||||
Uri webUri = (content == null) ? null : content.getWebUri();
|
||||
if (webUri != null) {
|
||||
intent.putExtra(ACTIONS_URL, webUri.toString());
|
||||
}
|
||||
if (mWebUrl != null) {
|
||||
intent.putExtra(ACTIONS_URL, mWebUrl);
|
||||
}
|
||||
|
||||
return intent;
|
||||
@@ -190,26 +169,6 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic AssistDataReceiver. This is passed to ActivityTaskManagerService, which then requests
|
||||
* the data.
|
||||
*/
|
||||
private static final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
|
||||
private TaskOverlayGo mOverlay;
|
||||
|
||||
public void setOverlay(TaskOverlayGo overlay) {
|
||||
mOverlay = overlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandleAssistData(Bundle data) {
|
||||
mOverlay.onAssistDataReceived(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandleAssistScreenshot(Bitmap screenshot) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
|
||||
* controller.
|
||||
|
||||
@@ -234,7 +234,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
getAppsView().getSearchUiManager().destroySearch();
|
||||
mHotseatPredictionController.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -102,9 +102,11 @@ public class WallpaperColorInfo implements OnColorsChangedListener {
|
||||
private void notifyChange() {
|
||||
// Create a new array to avoid concurrent modification when the activity destroys itself.
|
||||
mTempListeners = mListeners.toArray(mTempListeners);
|
||||
for (OnChangeListener listener : mTempListeners) {
|
||||
for (int i = mTempListeners.length - 1; i >= 0; --i) {
|
||||
final OnChangeListener listener = mTempListeners[i];
|
||||
if (listener != null) {
|
||||
listener.onExtractedColorsChanged(this);
|
||||
mTempListeners[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
}
|
||||
|
||||
private void maybeSwipeInteractionToOverviewComplete() {
|
||||
if (mReachedOverview && mDetector.isSettlingState()) {
|
||||
if (mReachedOverview && !mDetector.isDraggingState()) {
|
||||
onSwipeInteractionCompleted(OVERVIEW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
|
||||
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
|
||||
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
|
||||
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
|
||||
import static com.android.quickstep.util.SwipePipToHomeAnimator.FRACTION_END;
|
||||
import static com.android.quickstep.util.SwipePipToHomeAnimator.FRACTION_START;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
|
||||
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
@@ -1073,7 +1075,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
homeAnimFactory, runningTaskTarget, start);
|
||||
mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
|
||||
mSwipePipToHomeAnimator.setInterpolator(interpolator);
|
||||
mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
|
||||
mSwipePipToHomeAnimator.setFloatValues(FRACTION_START, FRACTION_END);
|
||||
mSwipePipToHomeAnimator.start();
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
|
||||
} else {
|
||||
@@ -1483,8 +1485,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mRecentsAnimationController.setFinishTaskBounds(
|
||||
mSwipePipToHomeAnimator.getTaskId(),
|
||||
mSwipePipToHomeAnimator.getDestinationBounds(),
|
||||
mSwipePipToHomeAnimator.getFinishWindowCrop(),
|
||||
mSwipePipToHomeAnimator.getFinishTransform());
|
||||
mSwipePipToHomeAnimator.getFinishTransaction());
|
||||
mIsSwipingPipToHome = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.window.PictureInPictureSurfaceTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
@@ -149,14 +150,13 @@ public class RecentsAnimationController {
|
||||
* accordingly. This should be called before `finish`
|
||||
* @param taskId for which the leash should be updated
|
||||
* @param destinationBounds bounds of the final PiP window
|
||||
* @param windowCrop bounds to crop as part of final transform.
|
||||
* @param float9 An array of 9 floats to be used as matrix transform.
|
||||
* @param finishTransaction leash operations for the final transform.
|
||||
*/
|
||||
public void setFinishTaskBounds(int taskId, Rect destinationBounds, Rect windowCrop,
|
||||
float[] float9) {
|
||||
public void setFinishTaskBounds(int taskId, Rect destinationBounds,
|
||||
PictureInPictureSurfaceTransaction finishTransaction) {
|
||||
UI_HELPER_EXECUTOR.execute(
|
||||
() -> mController.setFinishTaskBounds(taskId, destinationBounds, windowCrop,
|
||||
float9));
|
||||
() -> mController.setFinishTaskBounds(taskId, destinationBounds,
|
||||
finishTransaction));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
import android.app.IAssistDataReceiver;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.util.Executors;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Can be used to request the AssistContent from a provided task id, useful for getting the web uri
|
||||
* if provided from the task.
|
||||
*/
|
||||
public class AssistContentRequester {
|
||||
private static final String TAG = "AssistContentRequester";
|
||||
private static final String ASSIST_KEY_CONTENT = "content";
|
||||
|
||||
/** For receiving content, called on the main thread. */
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when the {@link android.app.assist.AssistContent} of the requested task is
|
||||
* available.
|
||||
**/
|
||||
void onAssistContentAvailable(AssistContent assistContent);
|
||||
}
|
||||
|
||||
private final IActivityTaskManager mActivityTaskManager;
|
||||
private final String mPackageName;
|
||||
private final Executor mCallbackExecutor;
|
||||
|
||||
public AssistContentRequester(Context context) {
|
||||
mActivityTaskManager = ActivityTaskManager.getService();
|
||||
mPackageName = context.getApplicationContext().getPackageName();
|
||||
mCallbackExecutor = Executors.MAIN_EXECUTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the {@link AssistContent} from the task with the provided id.
|
||||
*
|
||||
* @param taskId to query for the content.
|
||||
* @param callback to call when the content is available, called on the main thread.
|
||||
*/
|
||||
public void requestAssistContent(int taskId, Callback callback) {
|
||||
try {
|
||||
mActivityTaskManager.requestAssistDataForTask(
|
||||
new AssistDataReceiver(callback, mCallbackExecutor), taskId, mPackageName);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final Callback mCallback;
|
||||
|
||||
AssistDataReceiver(Callback callback, Executor callbackExecutor) {
|
||||
mCallback = callback;
|
||||
mExecutor = callbackExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandleAssistData(Bundle data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
|
||||
if (content == null) {
|
||||
Log.e(TAG, "Received AssistData, but no AssistContent found");
|
||||
return;
|
||||
}
|
||||
|
||||
mExecutor.execute(() -> mCallback.onAssistContentAvailable(content));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandleAssistScreenshot(Bitmap screenshot) {}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.window.PictureInPictureSurfaceTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -46,10 +47,12 @@ import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
* Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
|
||||
* PiP window, which would ideally be on SysUI side as well.
|
||||
*/
|
||||
public class SwipePipToHomeAnimator extends ValueAnimator implements
|
||||
ValueAnimator.AnimatorUpdateListener {
|
||||
public class SwipePipToHomeAnimator extends ValueAnimator {
|
||||
private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
|
||||
|
||||
public static final float FRACTION_START = 0f;
|
||||
public static final float FRACTION_END = 1f;
|
||||
|
||||
private final int mTaskId;
|
||||
private final ComponentName mComponentName;
|
||||
private final SurfaceControl mLeash;
|
||||
@@ -139,7 +142,7 @@ public class SwipePipToHomeAnimator extends ValueAnimator implements
|
||||
mHasAnimationEnded = true;
|
||||
}
|
||||
});
|
||||
addUpdateListener(this);
|
||||
addUpdateListener(this::onAnimationUpdate);
|
||||
}
|
||||
|
||||
/** sets the from rotation if it's different from the target rotation. */
|
||||
@@ -167,48 +170,53 @@ public class SwipePipToHomeAnimator extends ValueAnimator implements
|
||||
mAppBounds.top + mDestinationBounds.height());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animator) {
|
||||
private void onAnimationUpdate(ValueAnimator animator) {
|
||||
if (mHasAnimationEnded) return;
|
||||
|
||||
final float fraction = animator.getAnimatedFraction();
|
||||
final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
|
||||
mDestinationBoundsAnimation);
|
||||
final SurfaceControl.Transaction tx =
|
||||
PipSurfaceTransactionHelper.newSurfaceControlTransaction();
|
||||
if (mSourceHintRectInsets == null) {
|
||||
// no source rect hint been set, directly scale the window down
|
||||
onAnimationScale(fraction, tx, bounds);
|
||||
} else {
|
||||
// scale and crop according to the source rect hint
|
||||
onAnimationScaleAndCrop(fraction, tx, bounds);
|
||||
}
|
||||
mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
|
||||
onAnimationUpdate(tx, animator.getAnimatedFraction());
|
||||
tx.apply();
|
||||
}
|
||||
|
||||
private PictureInPictureSurfaceTransaction onAnimationUpdate(SurfaceControl.Transaction tx,
|
||||
float fraction) {
|
||||
final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
|
||||
mDestinationBoundsAnimation);
|
||||
final PictureInPictureSurfaceTransaction op;
|
||||
if (mSourceHintRectInsets == null) {
|
||||
// no source rect hint been set, directly scale the window down
|
||||
op = onAnimationScale(fraction, tx, bounds);
|
||||
} else {
|
||||
// scale and crop according to the source rect hint
|
||||
op = onAnimationScaleAndCrop(fraction, tx, bounds);
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
/** scale the window directly with no source rect hint being set */
|
||||
private void onAnimationScale(float fraction, SurfaceControl.Transaction tx, Rect bounds) {
|
||||
private PictureInPictureSurfaceTransaction onAnimationScale(
|
||||
float fraction, SurfaceControl.Transaction tx, Rect bounds) {
|
||||
if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
|
||||
final RotatedPosition rotatedPosition = getRotatedPosition(fraction);
|
||||
mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
|
||||
return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
|
||||
rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
|
||||
} else {
|
||||
mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
|
||||
return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/** scale and crop the window with source rect hint */
|
||||
private void onAnimationScaleAndCrop(float fraction, SurfaceControl.Transaction tx,
|
||||
private PictureInPictureSurfaceTransaction onAnimationScaleAndCrop(
|
||||
float fraction, SurfaceControl.Transaction tx,
|
||||
Rect bounds) {
|
||||
final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
|
||||
mSourceHintRectInsets);
|
||||
if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
|
||||
final RotatedPosition rotatedPosition = getRotatedPosition(fraction);
|
||||
mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
|
||||
return mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
|
||||
rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
|
||||
} else {
|
||||
mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
|
||||
return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,34 +232,12 @@ public class SwipePipToHomeAnimator extends ValueAnimator implements
|
||||
return mDestinationBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Rect} of the final window crop in destination orientation.
|
||||
*/
|
||||
public Rect getFinishWindowCrop() {
|
||||
final Rect windowCrop = new Rect(mAppBounds);
|
||||
if (mSourceHintRectInsets != null) {
|
||||
windowCrop.inset(mSourceHintRectInsets);
|
||||
}
|
||||
return windowCrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array of 9 floats represents the final transform in destination orientation.
|
||||
*/
|
||||
public float[] getFinishTransform() {
|
||||
final Matrix transform = new Matrix();
|
||||
final float[] float9 = new float[9];
|
||||
if (mSourceHintRectInsets == null) {
|
||||
transform.setRectToRect(new RectF(mAppBounds), new RectF(mDestinationBounds),
|
||||
Matrix.ScaleToFit.FILL);
|
||||
} else {
|
||||
final float scale = mAppBounds.width() <= mAppBounds.height()
|
||||
? (float) mDestinationBounds.width() / mAppBounds.width()
|
||||
: (float) mDestinationBounds.height() / mAppBounds.height();
|
||||
transform.setScale(scale, scale);
|
||||
}
|
||||
transform.getValues(float9);
|
||||
return float9;
|
||||
/** @return {@link PictureInPictureSurfaceTransaction} for the final leash transaction. */
|
||||
public PictureInPictureSurfaceTransaction getFinishTransaction() {
|
||||
// get the final leash operations but do not apply to the leash.
|
||||
final SurfaceControl.Transaction tx =
|
||||
PipSurfaceTransactionHelper.newSurfaceControlTransaction();
|
||||
return onAnimationUpdate(tx, FRACTION_END);
|
||||
}
|
||||
|
||||
private RotatedPosition getRotatedPosition(float fraction) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<bool name="allow_rotation">true</bool>
|
||||
|
||||
<!-- Hotseat -->
|
||||
<bool name="hotseat_transpose_layout_with_orientation">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -3,7 +3,4 @@
|
||||
<!-- All Apps & Widgets -->
|
||||
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
|
||||
<integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
|
||||
|
||||
<!-- Hotseat -->
|
||||
<bool name="hotseat_transpose_layout_with_orientation">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -132,6 +132,9 @@
|
||||
<attr name="isScalable" format="boolean" />
|
||||
<attr name="devicePaddingId" format="reference" />
|
||||
|
||||
<!-- whether the grid option is shown to the user -->
|
||||
<attr name="visible" format="boolean" />
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DevicePadding">
|
||||
|
||||
@@ -329,6 +329,6 @@ public abstract class BaseDraggingActivity extends BaseActivity
|
||||
* views
|
||||
*/
|
||||
public SearchAdapterProvider createSearchAdapterProvider(AllAppsContainerView allapps) {
|
||||
return new DefaultSearchAdapterProvider(this);
|
||||
return new DefaultSearchAdapterProvider(this, allapps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.launcher3;
|
||||
import static android.animation.ValueAnimator.areAnimatorsEnabled;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -602,7 +601,7 @@ public class CellLayout extends ViewGroup {
|
||||
if (child instanceof BubbleTextView) {
|
||||
BubbleTextView bubbleChild = (BubbleTextView) child;
|
||||
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
|
||||
if (ENABLE_FOUR_COLUMNS.get()) {
|
||||
if (mActivity.getDeviceProfile().isScalableGrid) {
|
||||
bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,6 +626,8 @@ public class InvariantDeviceProfile {
|
||||
private final boolean isScalable;
|
||||
private final int devicePaddingId;
|
||||
|
||||
public final boolean visible;
|
||||
|
||||
private final SparseArray<TypedValue> extraAttrs;
|
||||
|
||||
public GridOption(Context context, AttributeSet attrs) {
|
||||
@@ -652,6 +654,8 @@ public class InvariantDeviceProfile {
|
||||
devicePaddingId = a.getResourceId(
|
||||
R.styleable.GridDisplayOption_devicePaddingId, 0);
|
||||
|
||||
visible = a.getBoolean(R.styleable.GridDisplayOption_visible, true);
|
||||
|
||||
a.recycle();
|
||||
|
||||
extraAttrs = Themes.createValueMap(context, attrs,
|
||||
|
||||
@@ -1781,9 +1781,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
@Override
|
||||
public Rect getFolderBoundingBox() {
|
||||
// We need to bound the folder to the currently visible workspace area
|
||||
Rect folderBoundingBox = new Rect();
|
||||
getWorkspace().getPageAreaRelativeToDragLayer(folderBoundingBox);
|
||||
return folderBoundingBox;
|
||||
return getWorkspace().getPageAreaRelativeToDragLayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
|
||||
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
|
||||
import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
|
||||
import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -37,10 +40,10 @@ import com.android.launcher3.notification.NotificationListener;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.pm.InstallSessionTracker;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
|
||||
@@ -122,6 +125,34 @@ public class LauncherAppState {
|
||||
mContext = context;
|
||||
|
||||
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
|
||||
|
||||
// b/175329686 Temporary logic to gracefully migrate group of users to the new 4x5 grid.
|
||||
String gridName = InvariantDeviceProfile.getCurrentGridName(context);
|
||||
if (ENABLE_FOUR_COLUMNS.get()
|
||||
|| "reasonable".equals(gridName)
|
||||
|| ENABLE_FOUR_COLUMNS.key.equals(gridName)) {
|
||||
// Reset flag and remove it from developer options to prevent it from being enabled
|
||||
// again.
|
||||
ENABLE_FOUR_COLUMNS.reset(context);
|
||||
FeatureFlags.removeFlag(ENABLE_FOUR_COLUMNS);
|
||||
|
||||
// Force migration code to run
|
||||
Utilities.getPrefs(context).edit()
|
||||
.remove(KEY_MIGRATION_SRC_HOTSEAT_COUNT)
|
||||
.remove(KEY_MIGRATION_SRC_WORKSPACE_SIZE)
|
||||
.apply();
|
||||
|
||||
// We make an empty call here to ensure the database is created with the old IDP grid,
|
||||
// so that when we set the new grid the migration can proceeds as expected.
|
||||
LauncherSettings.Settings.call(context.getContentResolver(), "");
|
||||
|
||||
String newGridName = "practical";
|
||||
Utilities.getPrefs(mContext).edit().putString("idp_grid_name", newGridName).commit();
|
||||
mInvariantDeviceProfile.setCurrentGrid(context, "practical");
|
||||
} else {
|
||||
FeatureFlags.removeFlag(ENABLE_FOUR_COLUMNS);
|
||||
}
|
||||
|
||||
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
|
||||
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
|
||||
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
|
||||
|
||||
@@ -15,6 +15,7 @@ public class LauncherFiles {
|
||||
private static final String XML = ".xml";
|
||||
|
||||
public static final String LAUNCHER_DB = "launcher.db";
|
||||
public static final String LAUNCHER_4_BY_5_DB = "launcher_4_by_5.db";
|
||||
public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
|
||||
public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
|
||||
public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
|
||||
@@ -30,6 +31,7 @@ public class LauncherFiles {
|
||||
|
||||
public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
|
||||
LAUNCHER_DB,
|
||||
LAUNCHER_4_BY_5_DB,
|
||||
LAUNCHER_4_BY_4_DB,
|
||||
LAUNCHER_3_BY_3_DB,
|
||||
LAUNCHER_2_BY_2_DB,
|
||||
|
||||
@@ -2000,16 +2000,26 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the area relative to dragLayer which is used to display a page.
|
||||
* Computes and returns the area relative to dragLayer which is used to display a page.
|
||||
* In case we have multiple pages displayed at the same time, we return the union of the areas.
|
||||
*/
|
||||
public void getPageAreaRelativeToDragLayer(Rect outArea) {
|
||||
CellLayout child = (CellLayout) getChildAt(getNextPage());
|
||||
if (child == null) {
|
||||
return;
|
||||
public Rect getPageAreaRelativeToDragLayer() {
|
||||
Rect area = new Rect();
|
||||
int nextPage = getNextPage();
|
||||
int panelCount = getPanelCount();
|
||||
for (int page = nextPage; page < nextPage + panelCount; page++) {
|
||||
CellLayout child = (CellLayout) getChildAt(page);
|
||||
if (child == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
|
||||
Rect tmpRect = new Rect();
|
||||
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, tmpRect);
|
||||
area.union(tmpRect);
|
||||
}
|
||||
|
||||
ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
|
||||
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, outArea);
|
||||
return area;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -77,7 +77,7 @@ import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePag
|
||||
public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
|
||||
Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener {
|
||||
|
||||
private static final float FLING_VELOCITY_MULTIPLIER = 135f;
|
||||
private static final float FLING_VELOCITY_MULTIPLIER = 1000 * .8f;
|
||||
// Starts the springs after at least 55% of the animation has passed.
|
||||
private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
|
||||
private static final int ALPHA_CHANNEL_COUNT = 2;
|
||||
@@ -140,12 +140,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
|
||||
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
|
||||
|
||||
addSpringView(R.id.all_apps_header);
|
||||
addSpringView(R.id.apps_list_view);
|
||||
addSpringView(R.id.all_apps_tabs_view_pager);
|
||||
|
||||
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,17 +164,10 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
return mWorkModeSwitch;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void setDampedScrollShift(float shift) {
|
||||
// Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
|
||||
float maxShift = getSearchView().getHeight() / 2f;
|
||||
super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
for (AdapterHolder holder : mAH) {
|
||||
holder.adapter.setAppsPerRow(dp.inv.numAllAppsColumns);
|
||||
if (holder.recyclerView != null) {
|
||||
// Remove all views and clear the pool, while keeping the data same. After this
|
||||
// call, all the viewHolders will be recreated.
|
||||
@@ -407,12 +395,6 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCanvasClipTopForOverscroll() {
|
||||
// Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
|
||||
return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
|
||||
}
|
||||
|
||||
private void rebindAdapters(boolean showTabs) {
|
||||
rebindAdapters(showTabs, false /* force */);
|
||||
}
|
||||
@@ -639,11 +621,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
if (shouldSpring
|
||||
&& valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
|
||||
int searchViewId = getSearchView().getId();
|
||||
addSpringView(searchViewId);
|
||||
finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
|
||||
(anim, canceled, value, velocity) -> removeSpringView(searchViewId));
|
||||
|
||||
absorbSwipeUpVelocity(Math.abs(
|
||||
Math.round(velocity * FLING_VELOCITY_MULTIPLIER)));
|
||||
shouldSpring = false;
|
||||
}
|
||||
}
|
||||
@@ -699,8 +678,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
applyPadding();
|
||||
setupOverlay();
|
||||
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
|
||||
recyclerView.addItemDecoration(new AllAppsSectionDecorator(
|
||||
AllAppsContainerView.this));
|
||||
recyclerView.addItemDecoration(mSearchAdapterProvider.getDecorator());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.allapps.search.SectionDecorationInfo;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
|
||||
@@ -108,7 +107,7 @@ public class AllAppsGridAdapter extends
|
||||
// The index of this app not including sections
|
||||
public int appIndex = -1;
|
||||
// Search section associated to result
|
||||
public SectionDecorationInfo sectionDecorationInfo = null;
|
||||
public DecorationInfo decorationInfo = null;
|
||||
|
||||
/**
|
||||
* Factory method for AppIcon AdapterItem
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.allapps.search.SectionDecorationInfo;
|
||||
import com.android.launcher3.util.Themes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ItemDecoration class that groups items in {@link AllAppsRecyclerView}
|
||||
*/
|
||||
public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
|
||||
|
||||
private final AllAppsContainerView mAppsView;
|
||||
|
||||
AllAppsSectionDecorator(AllAppsContainerView appsContainerView) {
|
||||
mAppsView = appsContainerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
|
||||
SearchAdapterProvider adapterProvider = mAppsView.getSearchAdapterProvider();
|
||||
for (int i = 0; i < parent.getChildCount(); i++) {
|
||||
View view = parent.getChildAt(i);
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
|
||||
if (adapterItem.sectionDecorationInfo != null) {
|
||||
SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
|
||||
SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
|
||||
if (decorationHandler != null) {
|
||||
if (view.equals(adapterProvider.getHighlightedItem())) {
|
||||
decorationHandler.onFocusDraw(c, view);
|
||||
} else {
|
||||
decorationHandler.onGroupDraw(c, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles grouping and drawing of items in the same all apps sections.
|
||||
*/
|
||||
public static class SectionDecorationHandler {
|
||||
protected RectF mBounds = new RectF();
|
||||
private final boolean mIsFullWidth;
|
||||
private final float mRadius;
|
||||
|
||||
protected final int mFocusColor; // main focused item color
|
||||
protected final int mFillcolor; // grouping color
|
||||
|
||||
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final boolean mIsTopRound;
|
||||
private final boolean mIsBottomRound;
|
||||
private float[] mCorners;
|
||||
private float mFillSpacing;
|
||||
|
||||
public SectionDecorationHandler(Context context, boolean isFullWidth, int fillAlpha,
|
||||
boolean isTopRound, boolean isBottomRound) {
|
||||
|
||||
mIsFullWidth = isFullWidth;
|
||||
int endScrim = Themes.getColorBackground(context);
|
||||
mFillcolor = ColorUtils.setAlphaComponent(endScrim, fillAlpha);
|
||||
mFocusColor = endScrim;
|
||||
|
||||
mIsTopRound = isTopRound;
|
||||
mIsBottomRound = isBottomRound;
|
||||
|
||||
mRadius = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.search_decoration_corner_radius);
|
||||
mFillSpacing = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.search_decoration_padding);
|
||||
mCorners = new float[]{
|
||||
mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top left radius in px
|
||||
mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top right radius in px
|
||||
mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0, // Bottom right
|
||||
mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0 // Bottom left
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw bounds onto canvas.
|
||||
*/
|
||||
public void onGroupDraw(Canvas canvas, View view) {
|
||||
if (view == null) return;
|
||||
mPaint.setColor(mFillcolor);
|
||||
mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||
onDraw(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the bound of the view to the canvas.
|
||||
*/
|
||||
public void onFocusDraw(Canvas canvas, @Nullable View view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
mPaint.setColor(mFocusColor);
|
||||
mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||
onDraw(canvas);
|
||||
}
|
||||
|
||||
|
||||
private void onDraw(Canvas canvas) {
|
||||
final Path path = new Path();
|
||||
RectF finalBounds = new RectF(mBounds.left + mFillSpacing,
|
||||
mBounds.top + mFillSpacing,
|
||||
mBounds.right - mFillSpacing,
|
||||
mBounds.bottom - mFillSpacing);
|
||||
path.addRoundRect(finalBounds, mCorners, Path.Direction.CW);
|
||||
canvas.drawPath(path, mPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset view bounds to empty.
|
||||
*/
|
||||
public void reset() {
|
||||
mBounds.setEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
|
||||
import com.android.launcher3.allapps.search.SectionDecorationInfo;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
@@ -288,11 +287,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
|
||||
mFastScrollerSections.clear();
|
||||
mAdapterItems.clear();
|
||||
|
||||
SectionDecorationInfo appSection = new SectionDecorationInfo();
|
||||
appSection.setDecorationHandler(
|
||||
new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true,
|
||||
0, false, false));
|
||||
|
||||
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
|
||||
// ordered set of sections
|
||||
|
||||
@@ -313,9 +307,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
|
||||
if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
|
||||
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
|
||||
}
|
||||
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
|
||||
appItem.sectionDecorationInfo = appSection;
|
||||
}
|
||||
|
||||
mAdapterItems.add(appItem);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
public class DecorationInfo {
|
||||
}
|
||||
@@ -48,11 +48,6 @@ public interface SearchUiManager {
|
||||
*/
|
||||
float getScrollRangeDelta(Rect insets);
|
||||
|
||||
/**
|
||||
* Called when activity is destroyed. Used to close search system services
|
||||
*/
|
||||
default void destroySearch() { }
|
||||
|
||||
/**
|
||||
* @return the edit text object
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
|
||||
@@ -136,7 +135,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
|
||||
mApps = appsView.getApps();
|
||||
mAppsView = appsView;
|
||||
mSearchBarController.initialize(
|
||||
new DefaultAppSearchAlgorithm(mLauncher, LauncherAppState.getInstance(mLauncher)),
|
||||
new DefaultAppSearchAlgorithm(mLauncher),
|
||||
this, mLauncher, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.allapps.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
|
||||
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BaseModelUpdateTask;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.search.StringMatcherUtility;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A device search section for handling app searches
|
||||
*/
|
||||
public class AppsSearchPipeline implements SearchPipeline {
|
||||
|
||||
private static final int MAX_RESULTS_COUNT = 5;
|
||||
|
||||
private final SectionDecorationInfo mSearchSectionInfo;
|
||||
private final LauncherAppState mLauncherAppState;
|
||||
|
||||
public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
|
||||
mLauncherAppState = launcherAppState;
|
||||
mSearchSectionInfo = new SectionDecorationInfo();
|
||||
mSearchSectionInfo.setDecorationHandler(
|
||||
new SectionDecorationHandler(context, true, 0, true, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void query(String input, Consumer<ArrayList<AdapterItem>> callback,
|
||||
CancellationSignal cancellationSignal) {
|
||||
mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
List<AppInfo> matchingResults = getTitleMatchResult(apps.data, input);
|
||||
callback.accept(getAdapterItems(matchingResults));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters {@link AppInfo}s matching specified query
|
||||
*/
|
||||
public static ArrayList<AppInfo> getTitleMatchResult(List<AppInfo> apps, String query) {
|
||||
// Do an intersection of the words in the query and each title, and filter out all the
|
||||
// apps that don't match all of the words in the query.
|
||||
final String queryTextLower = query.toLowerCase();
|
||||
final ArrayList<AppInfo> result = new ArrayList<>();
|
||||
StringMatcherUtility.StringMatcher matcher =
|
||||
StringMatcherUtility.StringMatcher.getInstance();
|
||||
for (AppInfo info : apps) {
|
||||
if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
|
||||
result.add(info);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
|
||||
ArrayList<AdapterItem> items = new ArrayList<>();
|
||||
for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
|
||||
AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i);
|
||||
appItem.sectionDecorationInfo = mSearchSectionInfo;
|
||||
items.add(appItem);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,39 @@
|
||||
*/
|
||||
package com.android.launcher3.allapps.search;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BaseModelUpdateTask;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.search.SearchAlgorithm;
|
||||
import com.android.launcher3.search.SearchCallback;
|
||||
import com.android.launcher3.search.StringMatcherUtility;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The default search implementation.
|
||||
*/
|
||||
public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
|
||||
|
||||
protected final Handler mResultHandler;
|
||||
private final AppsSearchPipeline mAppsSearchPipeline;
|
||||
private static final int MAX_RESULTS_COUNT = 5;
|
||||
|
||||
public DefaultAppSearchAlgorithm(Context context, LauncherAppState launcherAppState) {
|
||||
mResultHandler = new Handler();
|
||||
mAppsSearchPipeline = new AppsSearchPipeline(context, launcherAppState);
|
||||
private final LauncherAppState mAppState;
|
||||
private final Handler mResultHandler;
|
||||
|
||||
public DefaultAppSearchAlgorithm(Context context) {
|
||||
mAppState = LauncherAppState.getInstance(context);
|
||||
mResultHandler = new Handler(MAIN_EXECUTOR.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,11 +58,38 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSearch(final String query,
|
||||
final SearchCallback<AdapterItem> callback) {
|
||||
mAppsSearchPipeline.query(query,
|
||||
results -> mResultHandler.post(
|
||||
() -> callback.onSearchResult(query, results)),
|
||||
null);
|
||||
public void doSearch(String query, SearchCallback<AdapterItem> callback) {
|
||||
mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
|
||||
mResultHandler.post(() -> callback.onSearchResult(query, result));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters {@link AppInfo}s matching specified query
|
||||
*/
|
||||
@AnyThread
|
||||
public static ArrayList<AdapterItem> getTitleMatchResult(List<AppInfo> apps, String query) {
|
||||
// Do an intersection of the words in the query and each title, and filter out all the
|
||||
// apps that don't match all of the words in the query.
|
||||
final String queryTextLower = query.toLowerCase();
|
||||
final ArrayList<AdapterItem> result = new ArrayList<>();
|
||||
StringMatcherUtility.StringMatcher matcher =
|
||||
StringMatcherUtility.StringMatcher.getInstance();
|
||||
|
||||
int resultCount = 0;
|
||||
int total = apps.size();
|
||||
for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
|
||||
AppInfo info = apps.get(i);
|
||||
if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
|
||||
AdapterItem appItem = AdapterItem.asApp(resultCount, "", info, resultCount);
|
||||
result.add(appItem);
|
||||
resultCount++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
*/
|
||||
package com.android.launcher3.allapps.search;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
|
||||
@@ -29,10 +34,19 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
*/
|
||||
public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
|
||||
|
||||
private final RecyclerView.ItemDecoration mDecoration;
|
||||
private View mHighlightedView;
|
||||
|
||||
public DefaultSearchAdapterProvider(BaseDraggingActivity launcher) {
|
||||
super(launcher);
|
||||
public DefaultSearchAdapterProvider(BaseDraggingActivity launcher,
|
||||
AllAppsContainerView appsContainerView) {
|
||||
super(launcher, appsContainerView);
|
||||
mDecoration = new RecyclerView.ItemDecoration() {
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent,
|
||||
@NonNull RecyclerView.State state) {
|
||||
super.onDraw(c, parent, state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,4 +81,9 @@ public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
|
||||
public View getHighlightedItem() {
|
||||
return mHighlightedView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ItemDecoration getDecorator() {
|
||||
return mDecoration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter;
|
||||
|
||||
/**
|
||||
@@ -31,7 +34,7 @@ public abstract class SearchAdapterProvider {
|
||||
|
||||
protected final BaseDraggingActivity mLauncher;
|
||||
|
||||
public SearchAdapterProvider(BaseDraggingActivity launcher) {
|
||||
public SearchAdapterProvider(BaseDraggingActivity launcher, AllAppsContainerView appsView) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
@@ -72,7 +75,7 @@ public abstract class SearchAdapterProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* handles selection event on search adapter item. Returns false if provider can not handle
|
||||
* Handles selection event on search adapter item. Returns false if provider can not handle
|
||||
* event
|
||||
*/
|
||||
public abstract boolean launchHighlightedItem();
|
||||
@@ -82,5 +85,8 @@ public abstract class SearchAdapterProvider {
|
||||
*/
|
||||
public abstract View getHighlightedItem();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the item decorator.
|
||||
*/
|
||||
public abstract RecyclerView.ItemDecoration getDecorator();
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.allapps.search;
|
||||
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An interface for handling search within pipeline
|
||||
*/
|
||||
// Remove when System Service API is added.
|
||||
public interface SearchPipeline {
|
||||
|
||||
/**
|
||||
* Perform query
|
||||
*/
|
||||
void query(String input,
|
||||
Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
|
||||
CancellationSignal cancellationSignal);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.allapps.search;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
|
||||
|
||||
/**
|
||||
* Info class for a search section that is primarily used for decoration.
|
||||
*/
|
||||
public class SectionDecorationInfo {
|
||||
public static final int GROUPING = 1 << 1;
|
||||
|
||||
private String mSectionId;
|
||||
private SectionDecorationHandler mDecorationHandler;
|
||||
|
||||
public SectionDecorationInfo() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public SectionDecorationInfo(String sectionId) {
|
||||
mSectionId = sectionId;
|
||||
}
|
||||
|
||||
public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
|
||||
mDecorationHandler = sectionDecorationHandler;
|
||||
}
|
||||
|
||||
public SectionDecorationHandler getDecorationHandler() {
|
||||
return mDecorationHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section's ID
|
||||
*/
|
||||
public String getSectionId() {
|
||||
return mSectionId == null ? "" : mSectionId;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ public final class FeatureFlags {
|
||||
"ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
|
||||
|
||||
public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
|
||||
"ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
|
||||
"ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
|
||||
|
||||
// Keep as DeviceFlag to allow remote disable in emergency.
|
||||
public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
|
||||
@@ -187,7 +187,7 @@ public final class FeatureFlags {
|
||||
"EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
|
||||
+ "Any apps occupying the first row will be removed from workspace.");
|
||||
|
||||
public static final BooleanFlag ENABLE_FOUR_COLUMNS = new DeviceFlag(
|
||||
public static final DeviceFlag ENABLE_FOUR_COLUMNS = new DeviceFlag(
|
||||
"ENABLE_FOUR_COLUMNS", false, "Uses 4 columns in launcher grid."
|
||||
+ "Warning: This will permanently alter your home screen items and is not reversible.");
|
||||
|
||||
@@ -227,6 +227,12 @@ public final class FeatureFlags {
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeFlag(DebugFlag flag) {
|
||||
synchronized (sDebugFlags) {
|
||||
sDebugFlags.remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
static List<DebugFlag> getDebugFlags() {
|
||||
synchronized (sDebugFlags) {
|
||||
return new ArrayList<>(sDebugFlags);
|
||||
@@ -304,6 +310,15 @@ public final class FeatureFlags {
|
||||
.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets value to default value.
|
||||
*/
|
||||
public void reset(Context context) {
|
||||
mCurrentValue = defaultValue;
|
||||
context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(key, defaultValue).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringBuilder appendProps(StringBuilder src) {
|
||||
return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue);
|
||||
|
||||
@@ -90,7 +90,10 @@ public class GridCustomizationsProvider extends ContentProvider {
|
||||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
|
||||
if ((type == XmlPullParser.START_TAG)
|
||||
&& GridOption.TAG_NAME.equals(parser.getName())) {
|
||||
result.add(new GridOption(getContext(), Xml.asAttributeSet(parser)));
|
||||
GridOption option = new GridOption(getContext(), Xml.asAttributeSet(parser));
|
||||
if (option.visible) {
|
||||
result.add(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
|
||||
@@ -15,51 +15,25 @@
|
||||
*/
|
||||
package com.android.launcher3.views;
|
||||
|
||||
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.View;
|
||||
import android.widget.EdgeEffect;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
|
||||
/**
|
||||
* View group to allow rendering overscroll effect in a child at the parent level
|
||||
*/
|
||||
public class SpringRelativeLayout extends RelativeLayout {
|
||||
|
||||
private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
|
||||
private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
|
||||
private static final float VELOCITY_MULTIPLIER = 0.3f;
|
||||
|
||||
private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
|
||||
new FloatPropertyCompat<SpringRelativeLayout>("value") {
|
||||
|
||||
@Override
|
||||
public float getValue(SpringRelativeLayout object) {
|
||||
return object.mDampedScrollShift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(SpringRelativeLayout object, float value) {
|
||||
object.setDampedScrollShift(value);
|
||||
}
|
||||
};
|
||||
|
||||
protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
|
||||
private final SpringAnimation mSpring;
|
||||
|
||||
private float mDampedScrollShift = 0;
|
||||
private SpringEdgeEffect mActiveEdge;
|
||||
private final EdgeEffect mEdgeGlowTop;
|
||||
private final EdgeEffect mEdgeGlowBottom;
|
||||
|
||||
public SpringRelativeLayout(Context context) {
|
||||
this(context, null);
|
||||
@@ -71,98 +45,73 @@ public class SpringRelativeLayout extends RelativeLayout {
|
||||
|
||||
public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
|
||||
mSpring.setSpring(new SpringForce(0)
|
||||
.setStiffness(STIFFNESS)
|
||||
.setDampingRatio(DAMPING_RATIO));
|
||||
}
|
||||
|
||||
public void addSpringView(int id) {
|
||||
mSpringViews.put(id, true);
|
||||
}
|
||||
|
||||
public void removeSpringView(int id) {
|
||||
mSpringViews.delete(id);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to clip the canvas when drawing child views during overscroll.
|
||||
*/
|
||||
public int getCanvasClipTopForOverscroll() {
|
||||
return 0;
|
||||
mEdgeGlowTop = Utilities.ATLEAST_S
|
||||
? new EdgeEffect(context, attrs) : new EdgeEffect(context);
|
||||
mEdgeGlowBottom = Utilities.ATLEAST_S
|
||||
? new EdgeEffect(context, attrs) : new EdgeEffect(context);
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
||||
if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
|
||||
int saveCount = canvas.save();
|
||||
|
||||
canvas.clipRect(0, getCanvasClipTopForOverscroll(), getWidth(), getHeight());
|
||||
canvas.translate(0, mDampedScrollShift);
|
||||
boolean result = super.drawChild(canvas, child, drawingTime);
|
||||
|
||||
canvas.restoreToCount(saveCount);
|
||||
|
||||
return result;
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
if (!mEdgeGlowTop.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
canvas.translate(0, 0);
|
||||
mEdgeGlowTop.setSize(getWidth(), getHeight());
|
||||
if (mEdgeGlowTop.draw(canvas)) {
|
||||
postInvalidateOnAnimation();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
return super.drawChild(canvas, child, drawingTime);
|
||||
}
|
||||
|
||||
private void setActiveEdge(SpringEdgeEffect edge) {
|
||||
if (mActiveEdge != edge && mActiveEdge != null) {
|
||||
mActiveEdge.mDistance = 0;
|
||||
}
|
||||
mActiveEdge = edge;
|
||||
}
|
||||
|
||||
protected void setDampedScrollShift(float shift) {
|
||||
if (shift != mDampedScrollShift) {
|
||||
mDampedScrollShift = shift;
|
||||
invalidate();
|
||||
if (!mEdgeGlowBottom.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
canvas.translate(-width, height);
|
||||
canvas.rotate(180, width, 0);
|
||||
mEdgeGlowBottom.setSize(width, height);
|
||||
if (mEdgeGlowBottom.draw(canvas)) {
|
||||
postInvalidateOnAnimation();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishScrollWithVelocity(float velocity) {
|
||||
mSpring.setStartVelocity(velocity);
|
||||
mSpring.setStartValue(mDampedScrollShift);
|
||||
mSpring.start();
|
||||
}
|
||||
|
||||
protected void finishWithShiftAndVelocity(float shift, float velocity,
|
||||
DynamicAnimation.OnAnimationEndListener listener) {
|
||||
setDampedScrollShift(shift);
|
||||
mSpring.addEndListener(listener);
|
||||
finishScrollWithVelocity(velocity);
|
||||
/**
|
||||
* Absorbs the velocity as a result for swipe-up fling
|
||||
*/
|
||||
protected void absorbSwipeUpVelocity(int velocity) {
|
||||
mEdgeGlowBottom.onAbsorb(velocity);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public EdgeEffectFactory createEdgeEffectFactory() {
|
||||
return new SpringEdgeEffectFactory();
|
||||
return new ProxyEdgeEffectFactory();
|
||||
}
|
||||
|
||||
private class SpringEdgeEffectFactory extends EdgeEffectFactory {
|
||||
private class ProxyEdgeEffectFactory extends EdgeEffectFactory {
|
||||
|
||||
@NonNull @Override
|
||||
protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
|
||||
switch (direction) {
|
||||
case DIRECTION_TOP:
|
||||
return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
|
||||
return new EdgeEffectProxy(getContext(), mEdgeGlowTop);
|
||||
case DIRECTION_BOTTOM:
|
||||
return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
|
||||
return new EdgeEffectProxy(getContext(), mEdgeGlowBottom);
|
||||
}
|
||||
return super.createEdgeEffect(view, direction);
|
||||
}
|
||||
}
|
||||
|
||||
private class SpringEdgeEffect extends EdgeEffect {
|
||||
private class EdgeEffectProxy extends EdgeEffect {
|
||||
|
||||
private final float mVelocityMultiplier;
|
||||
private final EdgeEffect mParent;
|
||||
|
||||
private float mDistance;
|
||||
|
||||
public SpringEdgeEffect(Context context, float velocityMultiplier) {
|
||||
EdgeEffectProxy(Context context, EdgeEffect parent) {
|
||||
super(context);
|
||||
mVelocityMultiplier = velocityMultiplier;
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,22 +119,44 @@ public class SpringRelativeLayout extends RelativeLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invalidateParentScrollEffect() {
|
||||
if (!mParent.isFinished()) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbsorb(int velocity) {
|
||||
finishScrollWithVelocity(velocity * mVelocityMultiplier);
|
||||
mParent.onAbsorb(velocity);
|
||||
invalidateParentScrollEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPull(float deltaDistance) {
|
||||
mParent.onPull(deltaDistance);
|
||||
invalidateParentScrollEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPull(float deltaDistance, float displacement) {
|
||||
setActiveEdge(this);
|
||||
mDistance += deltaDistance * (mVelocityMultiplier / 3f);
|
||||
setDampedScrollShift(mDistance * getHeight());
|
||||
mParent.onPull(deltaDistance, displacement);
|
||||
invalidateParentScrollEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {
|
||||
mDistance = 0;
|
||||
finishScrollWithVelocity(0);
|
||||
mParent.onRelease();
|
||||
invalidateParentScrollEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
mParent.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return mParent.isFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,17 +144,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
findViewById(R.id.tab_work)
|
||||
.setOnClickListener((View view) -> mViewPager.snapToPage(1));
|
||||
fastScroller.setIsRecyclerViewFirstChildInParent(false);
|
||||
springLayout.addSpringView(R.id.primary_widgets_list_view);
|
||||
springLayout.addSpringView(R.id.work_widgets_list_view);
|
||||
} else {
|
||||
mViewPager = null;
|
||||
springLayout.addSpringView(R.id.primary_widgets_list_view);
|
||||
}
|
||||
|
||||
layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
|
||||
true);
|
||||
springLayout.addSpringView(R.id.search_and_recommendations_container);
|
||||
|
||||
mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
|
||||
findViewById(R.id.search_and_recommendations_container));
|
||||
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.android.launcher3.views.WorkEduView;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
@@ -90,7 +89,7 @@ public class WorkTabTest extends AbstractLauncherUiTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void workTabExists() {
|
||||
mDevice.pressHome();
|
||||
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
|
||||
@@ -102,7 +101,7 @@ public class WorkTabTest extends AbstractLauncherUiTest {
|
||||
launcher -> launcher.getAppsView().isWorkTabVisible(), 60000);
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void toggleWorks() {
|
||||
mDevice.pressHome();
|
||||
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
|
||||
@@ -133,7 +132,7 @@ public class WorkTabTest extends AbstractLauncherUiTest {
|
||||
l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void testWorkEduFlow() {
|
||||
mDevice.pressHome();
|
||||
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
|
||||
@@ -176,7 +175,7 @@ public class WorkTabTest extends AbstractLauncherUiTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void testWorkEduIntermittent() {
|
||||
mDevice.pressHome();
|
||||
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
|
||||
|
||||
Reference in New Issue
Block a user