Merging from ub-launcher3-master @ build 6925377

Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-master_master_6925377.html

Change-Id: I928b100c8f41abff34047df69d988622123f9939
This commit is contained in:
Winson Chung 2020-10-23 09:32:16 -07:00
commit 87f2b09072
100 changed files with 3111 additions and 2461 deletions

View File

@ -36,14 +36,12 @@ java_library_static {
name: "launcher_log_protos_lite",
srcs: [
"protos/*.proto",
"proto_overrides/*.proto",
],
sdk_version: "current",
proto: {
type: "lite",
local_include_dirs:[
"protos",
"proto_overrides",
],
},
static_libs: ["libprotobuf-java-lite"],

View File

@ -30,6 +30,7 @@
with some minor changed based on the derivative app.
-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />

View File

@ -1,38 +1,42 @@
160759508
144170434
144170434
161801331
162564471
160361464
161901771
160568387
162623012
162012217
160718310
160361464
160718310
162012217
160748731
154951045
162861289
149934536
149934536
144170434
144170434
162454040
161273376
149934536
149934536
158701272
162871508
149934536
161939759
154964045
161536946
149934536
161685099
162812884
162480567
162480567
149934536
149934536
171450807
170675311
170338029
170338170
160544577
171171594
170488559
171131394
171131394
171026321
170648272
170752716
170611866
170702596
170487752
170665892
168608912
170636685
169771796
141126144
166614700
168805872
170263425
169221288
143965596
169221287
167259591
156044202
169438169
164926736
168653219
169963211
170121063
169988381
169980192
169221288
169385783
168167693
169796517
169330678
168818961
168608912

View File

@ -1,24 +1,39 @@
144170434
149934536
154951045
154964045
158701272
160361464
160568387
160718310
160748731
160759508
161273376
161536946
161685099
161801331
161901771
161939759
162012217
162454040
162480567
162564471
162623012
162812884
162861289
162871508
141126144
143965596
156044202
160544577
164926736
166614700
167259591
168167693
168608912
168653219
168805872
168818961
169221287
169221288
169330678
169385783
169438169
169771796
169796517
169963211
169980192
169988381
170121063
170263425
170338029
170338170
170487752
170488559
170611866
170636685
170648272
170665892
170675311
170702596
170752716
171026321
171131394
171171594
171450807

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +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.
*/
syntax = "proto2";
option java_package = "com.android.launcher3.userevent";
option java_outer_classname = "LauncherLogExtensions";
package userevent;
//
// Use this to add any app specific extensions to the proto.
//
message LauncherEventExtension {
}
message TargetExtension {
}

View File

@ -28,4 +28,40 @@ message LauncherTraceProto {
message TouchInteractionServiceProto {
optional bool service_connected = 1;
optional OverviewComponentObserverProto overview_component_obvserver = 2;
optional InputConsumerProto input_consumer = 3;
}
message OverviewComponentObserverProto {
optional bool overview_activity_started = 1;
optional bool overview_activity_resumed = 2;
}
message InputConsumerProto {
optional string name = 1;
optional SwipeHandlerProto swipe_handler = 2;
}
message SwipeHandlerProto {
optional GestureStateProto gesture_state = 1;
optional bool is_recents_attached_to_app_window = 2;
optional int32 scroll_offset = 3;
// Swipe up progress from 0 (app) to 1 (overview); can be > 1 if swiping past overview.
optional float app_to_overview_progress = 4;
}
message GestureStateProto {
optional GestureEndTarget endTarget = 1 [default = UNSET];
enum GestureEndTarget {
UNSET = 0;
HOME = 1;
RECENTS = 2;
NEW_TASK = 3;
LAST_TASK = 4;
}
}

View File

@ -51,6 +51,8 @@
<dimen name="recents_empty_message_text_size">16sp</dimen>
<dimen name="recents_empty_message_text_padding">16dp</dimen>
<dimen name="max_shadow_radius">5dp</dimen>
<!-- Total space (start + end) between the task card and the edge of the screen
in various configurations -->
<dimen name="task_card_vert_space">40dp</dimen>

View File

@ -16,14 +16,10 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
@ -32,11 +28,8 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@ -53,60 +46,16 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
protected boolean isLaunchingFromRecents(@NonNull View v,
@Nullable RemoteAnimationTargetCompat[] targets) {
return mLauncher.getStateManager().getState().overviewUi
&& findTaskViewToLaunch(mLauncher, v, targets) != null;
&& findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
}
@Override
protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
RecentsView recentsView = mLauncher.getOverviewPanel();
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
mLauncher.getDepthController(), pa);
anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().moveToRestState();
mLauncher.getStateManager().reapplyState();
}
};
} else {
AnimatorPlaybackController controller =
mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL,
RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().goToState(NORMAL, false);
}
};
}
anim.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
// animation adds some listeners which need to be called before windowAnimEndListener
// (the ordering of listeners matter in this case).
mLauncher.getStateManager().setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
mLauncher.getDepthController());
}
@Override

View File

@ -153,6 +153,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
private final float mContentTransY;
private final float mWorkspaceTransY;
private final float mClosingWindowTransY;
private final float mMaxShadowRadius;
private DeviceProfile mDeviceProfile;
@ -186,6 +187,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
mLauncher.addOnDeviceProfileChangeListener(this);
}
@ -538,6 +540,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
EXAGGERATED_EASE);
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, windowRadius, 0,
RADIUS_DURATION, EXAGGERATED_EASE);
FloatProp mShadowRadius = new FloatProp(0, mMaxShadowRadius, 0,
APP_LAUNCH_DURATION, EXAGGERATED_EASE);
@Override
public void onUpdate(float percent) {
@ -600,7 +604,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
builder.withMatrix(matrix)
.withWindowCrop(crop)
.withAlpha(1f - mIconAlpha.value)
.withCornerRadius(mWindowRadius.value);
.withCornerRadius(mWindowRadius.value)
.withShadowRadius(mShadowRadius.value);
} else {
tmpPos.set(target.position.x, target.position.y);
if (target.localBounds != null) {
@ -752,6 +757,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
FloatProp mShadowRadius = new FloatProp(mMaxShadowRadius, 0, 0, duration,
DEACCEL_1_7);
@Override
public void onUpdate(float percent) {
@ -773,7 +780,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
matrix.postTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
.withAlpha(mAlpha.value)
.withCornerRadius(windowCornerRadius);
.withCornerRadius(windowCornerRadius)
.withShadowRadius(mShadowRadius.value);
} else {
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
@ -798,7 +806,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
}
private void addCujInstrumentation(Animator anim, int cuj, String transition) {
if (Trace.isEnabled()) {
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
@ -825,7 +832,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
}
});
}
}
/**
* Remote animation runner for animation from the app to Launcher, including recents.

View File

@ -108,6 +108,8 @@ public class PredictionRowView extends LinearLayout implements
AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
@Nullable private List<ItemInfo> mPendingPredictedItems;
public PredictionRowView(@NonNull Context context) {
this(context, null);
}
@ -164,6 +166,7 @@ public class PredictionRowView extends LinearLayout implements
}
mDecorationHandler.onDraw(canvas);
mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
}
mFocusHelper.draw(canvas);
super.dispatchDraw(canvas);
@ -203,6 +206,16 @@ public class PredictionRowView extends LinearLayout implements
* we can optimize by swapping them in place.
*/
public void setPredictedApps(List<ItemInfo> items) {
if (!mLauncher.isWorkspaceLoading() && isShown() && getWindowVisibility() == View.VISIBLE) {
mPendingPredictedItems = items;
return;
}
applyPredictedApps(items);
}
private void applyPredictedApps(List<ItemInfo> items) {
mPendingPredictedItems = null;
mPredictedApps.clear();
items.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
@ -341,4 +354,13 @@ public class PredictionRowView extends LinearLayout implements
public View getFocusedChild() {
return getChildAt(0);
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (mPendingPredictedItems != null && !isVisible) {
applyPredictedApps(mPendingPredictedItems);
}
}
}

View File

@ -251,6 +251,28 @@ public class HotseatPredictionController implements DragController.DragListener,
* Sets or updates the predicted items
*/
public void setPredictedItems(FixedContainerItems items) {
if (!mLauncher.isWorkspaceLoading()
&& mHotseat.isShown()
&& mHotseat.getWindowVisibility() == View.VISIBLE) {
mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> {
if (isVisible) {
return;
}
mHotseat.setOnVisibilityAggregatedCallback(null);
applyPredictedItems(items);
});
} else {
mHotseat.setOnVisibilityAggregatedCallback(null);
applyPredictedItems(items);
}
}
/**
* Sets or updates the predicted items only once the hotseat becomes hidden to the user
*/
private void applyPredictedItems(FixedContainerItems items) {
mPredictedItems = items.items;
if (mPredictedItems.isEmpty()) {
HotseatRestoreHelper.restoreBackup(mLauncher);

View File

@ -250,6 +250,9 @@ public class AppEventProducer implements StatsLogConsumer {
case PREDICTION_CONTAINER: {
return "predictions";
}
case SHORTCUTS_CONTAINER: {
return "deep-shortcuts";
}
case FOLDER: {
FolderContainer fc = ci.getFolder();
switch (fc.getParentContainerCase()) {

View File

@ -18,8 +18,7 @@ package com.android.launcher3.model;
import static android.content.ContentResolver.SCHEME_CONTENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static com.android.launcher3.Utilities.newContentObserver;
import android.annotation.TargetApi;
import android.app.RemoteAction;
@ -35,7 +34,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
@ -43,7 +42,7 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
@ -55,6 +54,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.BgObjectWithLooper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
@ -68,15 +68,11 @@ import java.util.Map;
* Data model for digital wellbeing status of apps.
*/
@TargetApi(Build.VERSION_CODES.Q)
public final class WellbeingModel {
public final class WellbeingModel extends BgObjectWithLooper {
private static final String TAG = "WellbeingModel";
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
private static final boolean DEBUG = false;
private static final int MSG_PACKAGE_ADDED = 1;
private static final int MSG_PACKAGE_REMOVED = 2;
private static final int MSG_FULL_REFRESH = 3;
private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
private static final int IN_MINIMAL_DEVICE = 2;
@ -98,9 +94,9 @@ public final class WellbeingModel {
private final Context mContext;
private final String mWellbeingProviderPkg;
private final Handler mWorkerHandler;
private final ContentObserver mContentObserver;
private Handler mWorkerHandler;
private ContentObserver mContentObserver;
private final Object mModelLock = new Object();
// Maps the action Id to the corresponding RemoteAction
@ -111,60 +107,73 @@ public final class WellbeingModel {
private WellbeingModel(final Context context) {
mContext = context;
mWorkerHandler =
new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage);
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
initializeInBackground("WellbeingHandler");
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (DEBUG || mIsInTest) {
Log.d(TAG, "ContentObserver.onChange() called with: selfChange = ["
+ selfChange + "], uri = [" + uri + "]");
}
Preconditions.assertUIThread();
if (uri.getPath().contains(PATH_ACTIONS)) {
// Wellbeing reports that app actions have changed.
updateWellbeingData();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
updateLauncherModel(context);
}
}
};
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
updateLauncherModel(context));
protected void onInitialized(Looper looper) {
mWorkerHandler = new Handler(looper);
mContentObserver = newContentObserver(mWorkerHandler, this::onWellbeingUriChanged);
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
context.registerReceiver(
new SimpleBroadcastReceiver(this::onWellbeingProviderChanged),
mContext.registerReceiver(
new SimpleBroadcastReceiver(t -> restartObserver()),
PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg,
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
Intent.ACTION_PACKAGE_RESTARTED));
Intent.ACTION_PACKAGE_RESTARTED),
null, mWorkerHandler);
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
filter);
mContext.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
filter, null, mWorkerHandler);
restartObserver();
}
}
@WorkerThread
private void onWellbeingUriChanged(Uri uri) {
Preconditions.assertNonUiThread();
if (DEBUG || mIsInTest) {
Log.d(TAG, "ContentObserver.onChange() called with: uri = [" + uri + "]");
}
if (uri.getPath().contains(PATH_ACTIONS)) {
// Wellbeing reports that app actions have changed.
updateAllPackages();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
return;
}
// Temporary bug fix for b/169771796. Wellbeing provides the layout configuration when
// minimal device is enabled. We always want to reload the configuration from Wellbeing
// since the layout configuration might have changed.
mContext.deleteDatabase(DB_NAME_MINIMAL_DEVICE);
final Bundle extras = new Bundle();
String dbFile;
if (isInMinimalDeviceMode()) {
dbFile = DB_NAME_MINIMAL_DEVICE;
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
} else {
dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
}
LauncherSettings.Settings.call(mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
dbFile, extras);
}
}
public void setInTest(boolean inTest) {
mIsInTest = inTest;
}
protected void onWellbeingProviderChanged(Intent intent) {
if (DEBUG || mIsInTest) {
Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]");
}
restartObserver();
}
@WorkerThread
private void restartObserver() {
final ContentResolver resolver = mContext.getContentResolver();
resolver.unregisterContentObserver(mContentObserver);
@ -179,7 +188,7 @@ public final class WellbeingModel {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
updateWellbeingData();
updateAllPackages();
}
@MainThread
@ -212,39 +221,6 @@ public final class WellbeingModel {
}
}
private void updateWellbeingData() {
mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
}
private void updateLauncherModel(@NonNull final Context context) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
reloadLauncherInNormalMode(context);
return;
}
mWorkerHandler.post(() -> {
if (isInMinimalDeviceMode()) {
reloadLauncherInMinimalMode(context);
} else {
reloadLauncherInNormalMode(context);
}
});
}
private void reloadLauncherInNormalMode(@NonNull final Context context) {
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
InvariantDeviceProfile.INSTANCE.get(context).dbFile);
}
private void reloadLauncherInMinimalMode(@NonNull final Context context) {
final Bundle extras = new Bundle();
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
DB_NAME_MINIMAL_DEVICE, extras);
}
private Uri.Builder apiBuilder() {
return new Uri.Builder()
.scheme(SCHEME_CONTENT)
@ -277,7 +253,8 @@ public final class WellbeingModel {
return false;
}
private boolean updateActions(String... packageNames) {
@WorkerThread
private boolean updateActions(String[] packageNames) {
if (packageNames.length == 0) {
return true;
}
@ -340,68 +317,51 @@ public final class WellbeingModel {
return true;
}
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_PACKAGE_REMOVED: {
String packageName = (String) msg.obj;
mWorkerHandler.removeCallbacksAndMessages(packageName);
synchronized (mModelLock) {
mPackageToActionId.remove(packageName);
}
return true;
}
case MSG_PACKAGE_ADDED: {
String packageName = (String) msg.obj;
mWorkerHandler.removeCallbacksAndMessages(packageName);
if (!updateActions(packageName)) {
scheduleRefreshRetry(msg);
}
return true;
}
case MSG_FULL_REFRESH: {
// Remove all existing messages
mWorkerHandler.removeCallbacksAndMessages(null);
final String[] packageNames = mContext.getSystemService(LauncherApps.class)
@WorkerThread
private void updateActionsWithRetry(int retryCount, @Nullable String packageName) {
String[] packageNames = TextUtils.isEmpty(packageName)
? mContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle()).stream()
.map(li -> li.getApplicationInfo().packageName).distinct()
.toArray(String[]::new);
if (!updateActions(packageNames)) {
scheduleRefreshRetry(msg);
}
return true;
}
}
return false;
}
.toArray(String[]::new)
: new String[] { packageName };
private void scheduleRefreshRetry(Message originalMsg) {
int retryCount = originalMsg.arg1;
mWorkerHandler.removeCallbacksAndMessages(packageName);
if (updateActions(packageNames)) {
return;
}
if (retryCount >= RETRY_TIMES_MS.length) {
// To many retries, skip
return;
}
Message msg = Message.obtain(originalMsg);
msg.arg1 = retryCount + 1;
mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]);
mWorkerHandler.postDelayed(
() -> updateActionsWithRetry(retryCount + 1, packageName),
packageName, RETRY_TIMES_MS[retryCount]);
}
@WorkerThread
private void updateAllPackages() {
updateActionsWithRetry(0, null);
}
@WorkerThread
private void onAppPackageChanged(Intent intent) {
if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]");
Preconditions.assertUIThread();
Preconditions.assertNonUiThread();
final String packageName = intent.getData().getSchemeSpecificPart();
if (packageName == null || packageName.length() == 0) {
// they sent us a bad intent
return;
}
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget();
mWorkerHandler.removeCallbacksAndMessages(packageName);
synchronized (mModelLock) {
mPackageToActionId.remove(packageName);
}
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget();
updateActionsWithRetry(0, packageName);
}
}

View File

@ -41,11 +41,14 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINI
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.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@ -78,6 +81,8 @@ import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
@ -90,6 +95,7 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
@ -107,6 +113,7 @@ import com.android.systemui.shared.system.TaskInfoCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
/**
@ -614,13 +621,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
updateSysUiFlags(mCurrentShift.value);
applyWindowTransform();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationTargets != null) {
LiveTileOverlay.INSTANCE.update(
mTaskViewSimulator.getCurrentRect(),
mTaskViewSimulator.getCurrentCornerRadius());
}
}
updateLauncherTransitionProgress();
}
@ -719,6 +719,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
@UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
InteractionJankMonitorWrapper.begin(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
notifyGestureStartedAsync();
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
@ -794,7 +796,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
private void onSettledOnEndTarget() {
switch (mGestureState.getEndTarget()) {
final GestureEndTarget endTarget = mGestureState.getEndTarget();
switch (endTarget) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify swipe-to-home (recents animation) is finished
@ -811,7 +814,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
mStateCallback.setState(STATE_RESUME_LAST_TASK);
break;
}
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
}
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
@ -1388,7 +1394,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
if (taskView != null && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
finishTransitionPosted = ViewUtils.postDraw(taskView,
finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
@ -1450,17 +1456,64 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
if (mRecentsAnimationController != null) {
mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
true /* screenshot */);
}
mRecentsView.onSwipeUpAnimationSuccess();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
this::launchOtherTaskInLiveTileMode);
}
SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
reset();
}
private void launchOtherTaskInLiveTileMode(RemoteAnimationTargetCompat appearedTaskTarget) {
TaskView taskView = mRecentsView.getTaskView(appearedTaskTarget.taskId);
if (taskView == null) {
return;
}
RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
mRecentsAnimationTargets.apps,
mRecentsAnimationTargets.apps.length + 1);
apps[apps.length - 1] = appearedTaskTarget;
boolean launcherClosing =
taskIsATargetWithMode(apps, mActivity.getTaskId(), MODE_CLOSING);
AnimatorSet anim = new AnimatorSet();
TaskViewUtils.composeRecentsLaunchAnimator(
anim, taskView, apps,
mRecentsAnimationTargets.wallpapers, launcherClosing,
mActivity.getStateManager(), mRecentsView,
mActivityInterface.getDepthController());
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animator) {
cleanUp(false);
}
@Override
public void onAnimationCancel(Animator animator) {
cleanUp(true);
}
private void cleanUp(boolean canceled) {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(false /* toRecents */,
null /* onFinishComplete */);
if (canceled) {
mRecentsAnimationController = null;
} else {
mActivityInterface.onLaunchTaskSuccess();
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
}
}
});
anim.start();
}
private void addLiveTileOverlay() {
if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
mRecentsView.setLiveTileOverlayAttached(true);
@ -1523,11 +1576,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
protected void startNewTask(Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
if (!mCanceled) {
TaskView nextTask = mRecentsView.getNextPageTaskView();
if (nextTask != null) {
@ -1556,7 +1604,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
mCanceled = false;
}
}
/**
* Runs the given {@param action} if the recents animation has already started, or queues it to
@ -1641,6 +1688,32 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
mTaskViewSimulator.apply(mTransformParams);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
LiveTileOverlay.INSTANCE.update(
mTaskViewSimulator.getCurrentRect(),
mTaskViewSimulator.getCurrentCornerRadius());
}
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param inputConsumerProto The parent of this proto message.
*/
public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
mGestureState.writeToProto(swipeHandlerProto);
swipeHandlerProto.setIsRecentsAttachedToAppWindow(
mAnimationFactory.isRecentsAttachedToAppWindow());
swipeHandlerProto.setScrollOffset(mRecentsView == null
? 0
: mRecentsView.getScrollOffset());
swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
inputConsumerProto.setSwipeHandler(swipeHandlerProto);
}
public interface Factory {

View File

@ -297,6 +297,10 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
* @param animate Whether to animate recents to/from its new attached state.
*/
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
default boolean isRecentsAttachedToAppWindow() {
return false;
}
}
class DefaultAnimationFactory implements AnimationFactory {
@ -388,6 +392,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
}
@Override
public boolean isRecentsAttachedToAppWindow() {
return mIsAttachedToWindow;
}
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();

View File

@ -26,6 +26,8 @@ import android.content.Intent;
import android.os.Build;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.GestureStateProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@ -46,19 +48,22 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
* Defines the end targets of a gesture and the associated state.
*/
public enum GestureEndTarget {
HOME(true, LAUNCHER_STATE_HOME, false),
HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
GestureStateProto.GestureEndTarget.NEW_TASK),
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true);
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
GestureStateProto.GestureEndTarget.LAST_TASK);
GestureEndTarget(boolean isLauncher, int containerType,
boolean recentsAttachedToAppWindow) {
GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
GestureStateProto.GestureEndTarget protoEndTarget) {
this.isLauncher = isLauncher;
this.containerType = containerType;
this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
this.protoEndTarget = protoEndTarget;
}
/** Whether the target is in the launcher activity. Implicitly, if the end target is going
@ -68,6 +73,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
public final int containerType;
/** Whether RecentsView should be attached to the window as we animate to this target */
public final boolean recentsAttachedToAppWindow;
/** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
public final GestureStateProto.GestureEndTarget protoEndTarget;
}
private static final String TAG = "GestureState";
@ -345,4 +352,17 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param swipeHandlerProto The parent of this proto message.
*/
public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
gestureStateProto.setEndTarget(mEndTarget == null
? GestureStateProto.GestureEndTarget.UNSET
: mEndTarget.protoEndTarget);
swipeHandlerProto.setGestureState(gestureStateProto);
}
}

View File

@ -21,6 +21,9 @@ import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
@TargetApi(Build.VERSION_CODES.O)
public interface InputConsumer {
@ -116,4 +119,21 @@ public interface InputConsumer {
}
return name.toString();
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param serviceProto The parent of this proto message.
*/
default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
inputConsumerProto.setName(getName());
writeToProtoInternal(inputConsumerProto);
serviceProto.setInputConsumer(inputConsumerProto);
}
/**
* @see #writeToProto - allows subclasses to write additional info to the proto.
*/
default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
}

View File

@ -35,6 +35,7 @@ import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@ -174,6 +175,9 @@ public class OverviewCommandHelper {
return;
}
InteractionJankMonitorWrapper.begin(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timout */);
// Otherwise, start overview.
mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),

View File

@ -35,6 +35,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.SparseIntArray;
import com.android.launcher3.tracing.OverviewComponentObserverProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.systemui.shared.system.PackageManagerWrapper;
@ -262,4 +264,17 @@ public final class OverviewComponentObserver {
pw.println(" overviewIntent=" + mOverviewIntent);
pw.println(" homeIntent=" + mCurrentHomeIntent);
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param serviceProto The parent of this proto message.
*/
public void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
OverviewComponentObserverProto.Builder overviewComponentObserver =
OverviewComponentObserverProto.newBuilder();
overviewComponentObserver.setOverviewActivityStarted(mActivityInterface.isStarted());
overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
}
}

View File

@ -25,6 +25,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@ -90,24 +91,6 @@ public class RecentsAnimationController {
}
}
/**
* Notifies the controller that we want to defer cancel until the next app transition starts.
* If {@param screenshot} is set, then we will receive a screenshot on the next
* {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
* {@link #cleanupScreenshot()} when that screenshot is no longer used.
*/
public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
mController.setDeferCancelUntilNextTransition(defer, screenshot);
}
/**
* Cleans up the screenshot previously returned from
* {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
*/
public void cleanupScreenshot() {
UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
}
/**
* Remove task remote animation target from
* {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
@ -151,6 +134,7 @@ public class RecentsAnimationController {
mOnFinishedListener.accept(this);
UI_HELPER_EXECUTOR.execute(() -> {
mController.finish(toRecents, sendUserLeaveHint);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
if (callback != null) {
MAIN_EXECUTOR.execute(callback);
}

View File

@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@ -66,6 +67,8 @@ public abstract class SwipeUpAnimationLogic {
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
protected final float mMaxShadowRadius;
protected AnimatorControllerWithResistance mWindowTransitionController;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
@ -80,6 +83,9 @@ public abstract class SwipeUpAnimationLogic {
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
mDeviceState.getRotationTouchHelper().getDisplayRotation());
mTaskViewSimulator.setDrawsBelowRecents(true);
mMaxShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.max_shadow_radius);
mTransformParams.setShadowRadius(mMaxShadowRadius);
}
protected void initTransitionEndpoints(DeviceProfile dp) {
@ -259,9 +265,11 @@ public abstract class SwipeUpAnimationLogic {
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float shadowRadius = Utilities.mapRange(progress, mMaxShadowRadius, 0);
mTransformParams
.setTargetAlpha(getWindowAlpha(progress))
.setCornerRadius(cornerRadius);
.setCornerRadius(cornerRadius)
.setShadowRadius(shadowRadius);
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
@ -272,7 +280,8 @@ public abstract class SwipeUpAnimationLogic {
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mCropRect)
.withCornerRadius(params.getCornerRadius());
.withCornerRadius(params.getCornerRadius())
.withShadowRadius(params.getShadowRadius());
}
@Override

View File

@ -17,6 +17,7 @@ package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
@ -31,6 +32,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.Consumer;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
private RecentsAnimationController mController;
@ -39,6 +42,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
/**
* Preloads the recents animation.
@ -88,22 +92,21 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
if (thumbnailData != null) {
// If a screenshot is provided, switch to the screenshot before cleaning up
activityInterface.switchRunningTaskViewToScreenshot(thumbnailData,
() -> cleanUpRecentsAnimation(thumbnailData));
} else {
cleanUpRecentsAnimation(null /* canceledThumbnail */);
}
cleanUpRecentsAnimation();
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
cleanUpRecentsAnimation(null /* canceledThumbnail */);
cleanUpRecentsAnimation();
}
@Override
public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
if (mLaunchOtherTaskHandler != null
&& mLastGestureState.getEndTarget() == RECENTS) {
mLaunchOtherTaskHandler.accept(appearedTaskTarget);
return;
}
if (mController != null) {
if (mLastAppearedTaskTarget == null
|| appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
@ -138,6 +141,15 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
return mCallbacks;
}
/**
* The passed-in handler is used to render side task launch animation in recents in live tile
* mode.
*/
public void setLaunchOtherTaskInLiveTileModeHandler(
Consumer<RemoteAnimationTargetCompat> handler) {
mLaunchOtherTaskHandler = handler;
}
/**
* Finishes the running recents animation.
*/
@ -147,7 +159,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
cleanUpRecentsAnimation(null /* canceledThumbnail */);
cleanUpRecentsAnimation();
}
}
@ -174,12 +186,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
/**
* Cleans up the recents animation entirely.
*/
private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) {
// Clean up the screenshot if necessary
if (mController != null && canceledThumbnail != null) {
mController.cleanupScreenshot();
}
private void cleanUpRecentsAnimation() {
// Release all the target leashes
if (mTargets != null) {
mTargets.release();
@ -195,6 +202,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mTargets = null;
mLastGestureState = null;
mLastAppearedTaskTarget = null;
mLaunchOtherTaskHandler = null;
}
public void dump() {

View File

@ -21,12 +21,14 @@ import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_REC
import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityManager;
@ -34,6 +36,7 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
@ -42,7 +45,6 @@ import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.TaskKeyLruCache;
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.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskDescriptionCompat;
@ -163,9 +165,8 @@ public class TaskIconCache {
key.getComponent(), key.userId);
}
if (activityInfo != null) {
entry.contentDescription = ActivityManagerWrapper.getInstance()
.getBadgedContentDescription(activityInfo, task.key.userId,
task.taskDescription);
entry.contentDescription = getBadgedContentDescription(
activityInfo, task.key.userId, task.taskDescription);
}
}
@ -173,6 +174,21 @@ public class TaskIconCache {
return entry;
}
private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
PackageManager pm = mContext.getPackageManager();
String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
if (TextUtils.isEmpty(taskLabel)) {
taskLabel = Utilities.trim(info.loadLabel(pm));
}
String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm));
String badgedApplicationLabel = userId != UserHandle.myUserId()
? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString()
: applicationLabel;
return applicationLabel.equals(taskLabel)
? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel;
}
@WorkerThread
private Drawable getDefaultIcon(int userId) {
synchronized (mDefaultIcons) {

View File

@ -17,15 +17,20 @@ package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
@ -35,12 +40,18 @@ import android.graphics.RectF;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
@ -49,6 +60,7 @@ import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@ -67,8 +79,7 @@ public final class TaskViewUtils {
* opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
*/
public static TaskView findTaskViewToLaunch(
BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
RecentsView recentsView = activity.getOverviewPanel();
RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets) {
if (v instanceof TaskView) {
TaskView taskView = (TaskView) v;
return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@ -119,6 +130,21 @@ public final class TaskViewUtils {
return taskView;
}
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
PendingAnimation out) {
boolean isRunningTask = v.isRunningTask();
TransformParams params = null;
TaskViewSimulator tsv = null;
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
params = v.getRecentsView().getLiveTileParams();
tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
}
createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets,
depthController, out, params, tsv);
}
/**
* Creates an animation that controls the window of the opening targets for the recents launch
* animation.
@ -126,16 +152,25 @@ public final class TaskViewUtils {
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
PendingAnimation out) {
PendingAnimation out, @Nullable TransformParams params,
@Nullable TaskViewSimulator tsv) {
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
boolean inLiveTileMode =
ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
new RemoteAnimationTargets(appTargets, wallpaperTargets,
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
if (params == null) {
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
targets.addReleaseCheck(applier);
TransformParams params = new TransformParams()
params = new TransformParams()
.setSyncTransactionApplier(applier)
.setTargetSet(targets);
}
final RecentsView recentsView = v.getRecentsView();
int taskIndex = recentsView.indexOfChild(v);
@ -149,8 +184,9 @@ public final class TaskViewUtils {
int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
TaskViewSimulator topMostSimulator = null;
if (targets.apps.length > 0) {
TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
if (tsv == null && targets.apps.length > 0) {
tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
tsv.setDp(dp);
tsv.setLayoutRotation(displayRotation, displayRotation);
tsv.setPreview(targets.apps[targets.apps.length - 1]);
@ -158,19 +194,24 @@ public final class TaskViewUtils {
tsv.recentsViewScale.value = 1;
tsv.setScroll(startScroll);
// Fade in the task during the initial 20% of the animation
out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
clampToProgress(LINEAR, 0, 0.2f));
}
if (tsv != null) {
out.setFloat(tsv.fullScreenProgress,
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tsv.recentsViewScale,
AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
out.addOnFrameCallback(() -> tsv.apply(params));
TaskViewSimulator finalTsv = tsv;
TransformParams finalParams = params;
out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
topMostSimulator = tsv;
}
// Fade in the task during the initial 20% of the animation
out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
@ -223,10 +264,19 @@ public final class TaskViewUtils {
});
}
out.addListener(new AnimatorListenerAdapter() {
out.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (isQuickSwitch) {
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
}
@Override
public void onAnimationEnd(Animator animation) {
targets.release();
super.onAnimationEnd(animation);
}
});
@ -235,4 +285,57 @@ public final class TaskViewUtils {
TOUCH_RESPONSE_INTERPOLATOR);
}
}
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
@NonNull DepthController depthController) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
depthController, pa);
anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
stateManager.moveToRestState();
stateManager.reapplyState();
}
};
} else {
AnimatorPlaybackController controller =
stateManager.createAnimationToNewWorkspace(NORMAL,
RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
stateManager.goToState(NORMAL, false);
}
};
}
anim.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
// animation adds some listeners which need to be called before windowAnimEndListener
// (the ordering of listeners matter in this case).
stateManager.setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
}
}

View File

@ -737,9 +737,12 @@ public class TouchInteractionService extends Service implements PluginListener<O
private void reset() {
mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
if (mInputEventReceiver != null) {
mInputEventReceiver.setBatchingEnabled(true);
}
}
private void preloadOverview(boolean fromInit) {
if (!mDeviceState.isUserUnlocked()) {
@ -895,6 +898,12 @@ public class TouchInteractionService extends Service implements PluginListener<O
TouchInteractionServiceProto.Builder serviceProto =
TouchInteractionServiceProto.newBuilder();
serviceProto.setServiceConnected(true);
if (mOverviewComponentObserver != null) {
mOverviewComponentObserver.writeToProto(serviceProto);
}
mConsumer.writeToProto(serviceProto);
proto.setTouchInteractionService(serviceProto);
}
}

View File

@ -15,21 +15,23 @@
*/
package com.android.quickstep;
import android.graphics.Canvas;
import android.os.Handler;
import android.view.View;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.ViewRootImplCompat;
import java.util.function.BooleanSupplier;
import java.util.function.LongConsumer;
/**
* Utility class for helpful methods related to {@link View} objects.
*/
public class ViewUtils {
/** See {@link #postDraw(View, Runnable, BooleanSupplier)}} */
public static boolean postDraw(View view, Runnable onFinishRunnable) {
return postDraw(view, onFinishRunnable, () -> false);
/** See {@link #postFrameDrawn(View, Runnable, BooleanSupplier)}} */
public static boolean postFrameDrawn(View view, Runnable onFinishRunnable) {
return postFrameDrawn(view, onFinishRunnable, () -> false);
}
/**
@ -38,37 +40,55 @@ public class ViewUtils {
*
* @param onFinishRunnable runnable to be run right after the view finishes drawing.
*/
public static boolean postDraw(View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
return new WindowCallbacksCompat(view) {
// The number of frames to defer until we actually finish the animation
private int mDeferFrameCount = 2;
public static boolean postFrameDrawn(
View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
return new FrameHandler(view, onFinishRunnable, canceled).schedule();
}
private static class FrameHandler implements LongConsumer {
final ViewRootImplCompat mViewRoot;
final Runnable mFinishCallback;
final BooleanSupplier mCancelled;
final Handler mHandler;
int mDeferFrameCount = 1;
FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) {
mViewRoot = new ViewRootImplCompat(view);
mFinishCallback = finishCallback;
mCancelled = cancelled;
mHandler = new Handler();
}
@Override
public void onPostDraw(Canvas canvas) {
// If we were cancelled after this was attached, do not update
// the state.
if (canceled.getAsBoolean()) {
detach();
public void accept(long l) {
Utilities.postAsyncCallback(mHandler, this::onFrame);
}
private void onFrame() {
if (mCancelled.getAsBoolean()) {
return;
}
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
// Workaround, detach and reattach to invalidate the root node for
// another draw
detach();
attach();
view.invalidate();
schedule();
return;
}
if (onFinishRunnable != null) {
onFinishRunnable.run();
if (mFinishCallback != null) {
mFinishCallback.run();
}
detach();
}
}.attach();
private boolean schedule() {
if (mViewRoot.isValid()) {
mViewRoot.registerRtFrameCallback(this);
mViewRoot.getView().invalidate();
return true;
}
return false;
}
}
}

View File

@ -4,6 +4,7 @@ import android.view.MotionEvent;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.quickstep.InputConsumer;
import com.android.systemui.shared.system.InputMonitorCompat;
@ -53,4 +54,9 @@ public abstract class DelegateInputConsumer implements InputConsumer {
mDelegate.onMotionEvent(event);
event.recycle();
}
@Override
public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
mDelegate.writeToProtoInternal(inputConsumerProto);
}
}

View File

@ -51,6 +51,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.R;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AbsSwipeUpHandler;
@ -478,4 +479,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
public boolean allowInterceptByParent() {
return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED);
}
@Override
public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
if (mInteractionHandler != null) {
mInteractionHandler.writeToProto(inputConsumerProto);
}
}
}

View File

@ -104,7 +104,13 @@ final class AssistantGestureTutorialController extends TutorialController {
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(ASSISTANT_COMPLETE));
() -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(ASSISTANT_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case ASSISTANT_NOT_STARTED_BAD_ANGLE:
showFeedback(R.string.assistant_gesture_feedback_swipe_not_diagonal);

View File

@ -130,7 +130,13 @@ final class BackGestureTutorialController extends TutorialController {
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE));
() -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case BACK_CANCELLED_FROM_LEFT:
showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);

View File

@ -15,8 +15,6 @@
*/
package com.android.quickstep.interaction;
import static com.android.quickstep.interaction.TutorialFragment.KEY_TUTORIAL_TYPE;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
@ -25,11 +23,14 @@ import android.view.Display;
import android.view.View;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/** Shows the gesture interactive sandbox in full screen mode. */
@ -37,6 +38,9 @@ public class GestureSandboxActivity extends FragmentActivity {
private static final String LOG_TAG = "GestureSandboxActivity";
private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
private Deque<TutorialType> mTutorialSteps;
private TutorialFragment mFragment;
@Override
@ -45,7 +49,9 @@ public class GestureSandboxActivity extends FragmentActivity {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.gesture_tutorial_activity);
mFragment = TutorialFragment.newInstance(getTutorialType(getIntent().getExtras()));
Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
mTutorialSteps = getTutorialSteps(args);
mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
getSupportFragmentManager().beginTransaction()
.add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
@ -72,17 +78,65 @@ public class GestureSandboxActivity extends FragmentActivity {
}
}
private TutorialType getTutorialType(Bundle extras) {
TutorialType defaultType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
@Override
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
super.onSaveInstanceState(savedInstanceState);
}
if (extras == null || !extras.containsKey(KEY_TUTORIAL_TYPE)) {
return defaultType;
/** Returns true iff there aren't anymore tutorial types to display to the user. */
public boolean isTutorialComplete() {
return mTutorialSteps.isEmpty();
}
try {
return TutorialType.valueOf(extras.getString(KEY_TUTORIAL_TYPE, ""));
} catch (IllegalArgumentException e) {
return defaultType;
/**
* Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
*
* If there is no following step, the tutorial is closed.
*/
public void continueTutorial() {
if (isTutorialComplete()) {
mFragment.closeTutorial();
return;
}
mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
getSupportFragmentManager().beginTransaction()
.replace(R.id.gesture_tutorial_fragment_container, mFragment)
.runOnCommit(() -> mFragment.onAttachedToWindow())
.commit();
}
private String[] getTutorialStepNames() {
String[] tutorialStepNames = new String[mTutorialSteps.size()];
int i = 0;
for (TutorialType tutorialStep : mTutorialSteps) {
tutorialStepNames[i++] = tutorialStep.name();
}
return tutorialStepNames;
}
private Deque<TutorialType> getTutorialSteps(Bundle extras) {
Deque<TutorialType> defaultSteps = new ArrayDeque<>();
defaultSteps.push(TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
return defaultSteps;
}
String[] tutorialStepNames = extras.getStringArray(KEY_TUTORIAL_STEPS);
if (tutorialStepNames == null) {
return defaultSteps;
}
Deque<TutorialType> tutorialSteps = new ArrayDeque<>();
for (String tutorialStepName : tutorialStepNames) {
tutorialSteps.addLast(TutorialType.valueOf(tutorialStepName));
}
return tutorialSteps;
}
private void hideSystemUI() {

View File

@ -94,8 +94,13 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
animateFakeTaskViewHome(finalVelocity, () ->
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
animateFakeTaskViewHome(finalVelocity, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:

View File

@ -104,8 +104,13 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () ->
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
fadeOutFakeTaskView(true, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:

View File

@ -54,6 +54,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
fragment = new BackGestureTutorialFragment();
tutorialType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
}
Bundle args = new Bundle();
args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
fragment.setArguments(args);
@ -197,6 +198,20 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return mHandCoachingAnimation;
}
void continueTutorial() {
if (!(getContext() instanceof GestureSandboxActivity)) {
closeTutorial();
return;
}
GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
if (gestureSandboxActivity == null) {
closeTutorial();
return;
}
gestureSandboxActivity.continueTutorial();
}
void closeTutorial() {
FragmentActivity activity = getActivity();
if (activity != null) {
@ -207,4 +222,13 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
void startSystemNavigationSetting() {
startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
}
boolean isTutorialComplete() {
if (!(getContext() instanceof GestureSandboxActivity)) {
return true;
}
GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
return gestureSandboxActivity == null || gestureSandboxActivity.isTutorialComplete();
}
}

View File

@ -23,6 +23,7 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static com.android.launcher3.Utilities.newContentObserver;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@ -73,12 +74,9 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private static final boolean DEBUG = false;
private static final String DELIMITER_DOT = "\\.";
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updateAutoRotateSetting();
}
};
private ContentObserver mSystemAutoRotateObserver =
newContentObserver(new Handler(), t -> updateAutoRotateSetting());
@Retention(SOURCE)
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
@ -541,6 +539,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
* @return "MyObject@1234"
*/
private static String extractObjectNameAndAddress(String stringToExtract) {
return stringToExtract.substring(stringToExtract.lastIndexOf(DELIMITER_DOT));
int index = stringToExtract.lastIndexOf(DELIMITER_DOT);
return index >= 0 ? stringToExtract.substring(index) : "";
}
}

View File

@ -322,7 +322,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mTmpCropRect)
.withCornerRadius(getCurrentCornerRadius());
.withCornerRadius(getCurrentCornerRadius())
.withShadowRadius(params.getShadowRadius());
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
// When relativeLayer = 0, it reverts the surfaces back to the original order.

View File

@ -57,6 +57,7 @@ public class TransformParams {
private float mProgress;
private float mTargetAlpha;
private float mCornerRadius;
private float mShadowRadius;
private RemoteAnimationTargets mTargetSet;
private SurfaceTransactionApplier mSyncTransactionApplier;
private SurfaceControl mRecentsSurface;
@ -68,6 +69,7 @@ public class TransformParams {
mProgress = 0;
mTargetAlpha = 1;
mCornerRadius = -1;
mShadowRadius = 0;
}
/**
@ -90,6 +92,14 @@ public class TransformParams {
return this;
}
/**
* Sets the shadow radius of the transformed window, in pixels.
*/
public TransformParams setShadowRadius(float shadowRadius) {
mShadowRadius = shadowRadius;
return this;
}
/**
* Specifies the alpha of the transformed window. Default is 1.
*/
@ -197,6 +207,10 @@ public class TransformParams {
return mCornerRadius;
}
public float getShadowRadius() {
return mShadowRadius;
}
public SurfaceControl getRecentsSurface() {
return mRecentsSurface;
}

View File

@ -65,6 +65,10 @@ public class LiveTileOverlay extends Drawable {
invalidateSelf();
}
public void update(float left, float top, float right, float bottom) {
mCurrentRect.set(left, top, right, bottom);
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
@ -94,7 +98,6 @@ public class LiveTileOverlay extends Drawable {
@Override
public void draw(Canvas canvas) {
if (mCurrentRect != null) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
if (mIcon != null && mIconAnimationProgress > 0f) {
canvas.save();
@ -107,7 +110,6 @@ public class LiveTileOverlay extends Drawable {
canvas.restore();
}
}
}
@Override
public void setAlpha(int i) { }

View File

@ -230,6 +230,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
view.setScaleX(scale);
view.setScaleY(scale);
view.mLastComputedTaskPushOutDistance = null;
view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
view.updatePageOffsets();
view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
}
@ -539,6 +540,9 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == GONE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true /* toRecents */, null);
}
updateTaskStackListenerState();
}
@ -873,6 +877,10 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
mLiveTileTaskViewSimulator.setOffsetY(0);
// Reset the live tile rect
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
}
if (mRunningTaskTileHidden) {
setRunningTaskHidden(mRunningTaskTileHidden);
@ -1292,19 +1300,26 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
}
public void showNextTask() {
TaskView runningTaskView = getRunningTaskView();
final TaskView runningTaskView = getRunningTaskView();
final TaskView targetTask;
if (runningTaskView == null) {
// Launch the first task
if (getTaskViewCount() > 0) {
getTaskViewAt(0).launchTask(true);
targetTask = getTaskViewAt(0);
} else {
return;
}
} else {
if (getNextTaskView() != null) {
getNextTaskView().launchTask(true);
final TaskView nextTask = getNextTaskView();
if (nextTask != null) {
targetTask = nextTask;
} else {
runningTaskView.launchTask(true);
targetTask = runningTaskView;
}
}
targetTask.setEndQuickswitchCuj(true);
targetTask.launchTask(true);
}
public void setRunningTaskIconScaledDown(boolean isScaledDown) {
@ -1976,6 +1991,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
TaskView task = getTaskViewAt(i);
mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
}
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
/**
@ -2254,6 +2270,10 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
return mLiveTileTaskViewSimulator;
}
public TransformParams getLiveTileParams() {
return mLiveTileParams;
}
// TODO: To be removed in a follow up CL
public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets recentsAnimationTargets) {
@ -2442,7 +2462,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
} else {
taskView.getThumbnail().refresh();
}
ViewUtils.postDraw(taskView, onFinishRunnable);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
} else {
onFinishRunnable.run();
}

View File

@ -39,6 +39,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
@ -65,7 +66,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@ -75,6 +75,7 @@ import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@ -82,10 +83,12 @@ import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
@ -175,7 +178,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private float mCurveScale;
private float mFullscreenProgress;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final BaseDraggingActivity mActivity;
private final StatefulActivity mActivity;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
@ -189,6 +192,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private CancellableTask mThumbnailLoadRequest;
private CancellableTask mIconLoadRequest;
private boolean mEndQuickswitchCuj;
// Order in which the footers appear. Lower order appear below higher order.
public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
private final FooterWrapper[] mFooters = new FooterWrapper[2];
@ -210,18 +215,31 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
mActivity = StatefulActivity.fromContext(context);
setOnClickListener((view) -> {
if (getTask() == null) {
return;
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (isRunningTask()) {
// TODO: Replace this animation with createRecentsWindowAnimator
createLaunchAnimationForRunningTask().start();
} else {
launchTask(true /* animate */);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
RecentsView recentsView = getRecentsView();
RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
AnimatorSet anim = new AnimatorSet();
TaskViewUtils.composeRecentsLaunchAnimator(
anim, this, targets.apps,
targets.wallpapers, true /* launcherClosing */,
mActivity.getStateManager(), recentsView,
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
recentsView.finishRecentsAnimation(false, null);
}
});
anim.start();
} else {
launchTask(true /* animate */);
}
@ -801,6 +819,14 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
return false;
}
public boolean isEndQuickswitchCuj() {
return mEndQuickswitchCuj;
}
public void setEndQuickswitchCuj(boolean endQuickswitchCuj) {
mEndQuickswitchCuj = endQuickswitchCuj;
}
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;

View File

@ -19,6 +19,6 @@
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="?android:attr/colorAccent"
android:fillColor="?android:attr/textColorTertiary"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,8 +15,7 @@
<!-- The top and bottom paddings are defined in this container, but since we want
the list view to span the full width (for touch interception purposes), we
will bake the left/right padding into that view's background itself. -->
<com.android.launcher3.allapps.LauncherAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
<com.android.launcher3.allapps.LauncherAllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:theme="?attr/allAppsTheme"
android:layout_width="match_parent"

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -13,16 +12,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.BubbleTextView
xmlns:android="http://schemas.android.com/apk/res/android"
<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/BaseIcon"
style="@style/BaseIcon.AllApps"
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stateListAnimator="@animator/all_apps_fastscroll_icon_anim"
launcher:iconDisplay="all_apps"
launcher:centerVertically="true"
android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
android:paddingRight="@dimen/dynamic_grid_cell_padding_x" />
launcher:centerVertically="true" />

View File

@ -26,7 +26,6 @@
android:clipChildren="true"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:paddingTop="@dimen/all_apps_header_top_padding"
launcher:pageIndicator="@+id/tabs" >
<include layout="@layout/all_apps_rv_layout" />

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.views.SearchResultIcon xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/BaseIcon.AllApps"
launcher:iconDisplay="all_apps"
launcher:centerVertically="true" />

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Projectza
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -67,7 +67,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:background="?android:attr/selectableItemBackground"
android:text="@string/search_action_try_now">
</Button>

View File

@ -12,27 +12,24 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.views.SearchResultSuggestRow xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TextHeadline"
android:id="@+id/section_title"
android:background="?android:attr/selectableItemBackground"
<com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/BaseIcon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:padding="4dp"
android:minHeight="48dp"
android:orientation="horizontal"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp">
android:textSize="16sp"
android:padding="@dimen/dynamic_grid_edge_margin"
launcher:iconDisplay="hero_app"
android:drawableTint="?android:attr/textColorPrimary"
launcher:customIcon="@drawable/ic_allapps_search"
launcher:iconSizeOverride="24dp"
launcher:matchTextInsetWithQuery="true"
launcher:layoutHorizontal="true"
android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
>
<TextView
android:id="@+id/title"
style="@style/TextTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:layout_marginBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
</com.android.launcher3.views.SearchResultSuggestRow>
</com.android.launcher3.views.SearchResultIconRow>

View File

@ -17,7 +17,9 @@
android:id="@+id/section_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@style/TextHeadline"
android:padding="4dp"
style="@style/TextHeadline"
android:paddingStart="4dp"
android:paddingBottom="2dp"
android:paddingTop="12dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
android:textSize="18sp" />

View File

@ -26,7 +26,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_setting"
android:tint="@android:color/black"
android:forceDarkAllowed="true"
android:padding="12dp"
android:background="?android:attr/selectableItemBackgroundBorderless" />

View File

@ -69,6 +69,13 @@
<attr name="folderDotColor" />
</declare-styleable>
<declare-styleable name="SearchResultIconRow">
<attr name="customIcon" format="reference" />
<attr name="matchTextInsetWithQuery" format="boolean" />
</declare-styleable>
<declare-styleable name="ShadowInfo">
<attr name="ambientShadowColor" format="color" />
<attr name="ambientShadowBlur" format="dimension" />

View File

@ -59,7 +59,7 @@
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
<string name="all_apps_search_bar_hint">Search apps</string>
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
<string name="all_apps_on_device_search_bar_hint">Search this phone and more...</string>
<string name="all_apps_on_device_search_bar_hint">Search this phone and more</string>
<!-- Loading apps text. [CHAR_LIMIT=50] -->
<string name="all_apps_loading_message">Loading apps&#8230;</string>
<!-- No-search-results text. [CHAR_LIMIT=50] -->

View File

@ -223,6 +223,16 @@
<item name="android:lines">1</item>
</style>
<!-- Base theme for AllApps BubbleTextViews -->
<style name="BaseIcon.AllApps" parent="BaseIcon">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:stateListAnimator">@animator/all_apps_fastscroll_icon_anim</item>
<item name="android:paddingLeft">@dimen/dynamic_grid_cell_padding_x</item>
<item name="android:paddingRight">@dimen/dynamic_grid_cell_padding_x</item>
</style>
<!-- Icon displayed on the workspace -->
<style name="BaseIcon.Workspace" >
<item name="android:shadowRadius">2.0</item>

View File

@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@ -27,15 +28,18 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Property;
@ -50,6 +54,8 @@ import androidx.core.graphics.ColorUtils;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
@ -60,6 +66,7 @@ import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@ -79,7 +86,7 @@ import java.text.NumberFormat;
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
IconLabelDotView, DraggableView, Reorderable {
IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@ -87,6 +94,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private static final int DISPLAY_HERO_APP = 5;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private static final float HIGHLIGHT_SCALE = 1.16f;
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@ -95,6 +104,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private float mScaleForReorderBounce = 1f;
protected final Paint mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mHighlightPath = new Path();
protected int mHighlightColor = Color.TRANSPARENT;
private final BlurMaskFilter mHighlightShadowFilter;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@Override
@ -208,6 +222,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
setEllipsize(TruncateAt.END);
setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
setTextAlpha(1f);
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_click_shadow);
mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER);
}
@Override
@ -421,8 +440,38 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public void onDraw(Canvas canvas) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mHighlightColor != Color.TRANSPARENT) {
int count = canvas.save();
drawFocusHighlight(canvas);
canvas.restoreToCount(count);
}
super.onDraw(canvas);
drawDotIfNecessary(canvas);
}
protected void drawFocusHighlight(Canvas canvas) {
boolean isBadged = getTag() instanceof ItemInfo && !Process.myUserHandle().equals(
((ItemInfo) getTag()).user);
float insetScale = (HIGHLIGHT_SCALE - 1) / 2;
canvas.translate(-getIconSize() * insetScale, -insetScale * getIconSize());
float outlineSize = getIconSize() * HIGHLIGHT_SCALE;
mHighlightPath.reset();
mHighlightPaint.reset();
getIconBounds(mDotParams.iconBounds);
getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left, mDotParams.iconBounds.top,
outlineSize / 2);
if (isBadged) {
float borderSize = outlineSize - getIconSize();
float badgeSize = LauncherIcons.getBadgeSizeForIconSize(getIconSize()) + borderSize;
float badgeInset = outlineSize - badgeSize;
getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left + badgeInset,
mDotParams.iconBounds.top + badgeInset, badgeSize / 2);
}
mHighlightPaint.setMaskFilter(mHighlightShadowFilter);
mHighlightPaint.setColor(mDotParams.color);
canvas.drawPath(mHighlightPath, mHighlightPaint);
mHighlightPaint.setMaskFilter(null);
mHighlightPaint.setColor(mHighlightColor);
canvas.drawPath(mHighlightPath, mHighlightPaint);
}
/**
@ -627,7 +676,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/**
* Sets the icon for this view based on the layout direction.
*/
private void setIcon(Drawable icon) {
protected void setIcon(Drawable icon) {
if (mIsIconVisible) {
applyCompoundDrawables(icon);
}
@ -787,10 +836,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public SafeCloseable prepareDrawDragView() {
int highlightColor = mHighlightColor;
mHighlightColor = Color.TRANSPARENT;
resetIconScale();
setForceHideDot(true);
return () -> {
};
return () -> mHighlightColor = highlightColor;
}
private void resetIconScale() {
@ -827,4 +877,17 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
});
iconUpdateAnimation.start();
}
@Override
public void decorate(int color) {
mHighlightColor = color;
invalidate();
}
@Override
public void removeDecoration() {
mHighlightColor = Color.TRANSPARENT;
invalidate();
}
}

View File

@ -25,6 +25,10 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import java.util.function.Consumer;
/**
* View class that represents the bottom row of the home screen.
*/
@ -34,6 +38,7 @@ public class Hotseat extends CellLayout implements Insettable {
private boolean mHasVerticalHotseat;
private Workspace mWorkspace;
private boolean mSendTouchToWorkspace;
@Nullable private Consumer<Boolean> mOnVisibilityAggregatedCallback;
public Hotseat(Context context) {
this(context, null);
@ -129,4 +134,18 @@ public class Hotseat extends CellLayout implements Insettable {
}
return event.getY() > getCellHeight();
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (mOnVisibilityAggregatedCallback != null) {
mOnVisibilityAggregatedCallback.accept(isVisible);
}
}
/** Sets a callback to be called onVisibilityAggregated */
public void setOnVisibilityAggregatedCallback(@Nullable Consumer<Boolean> callback) {
mOnVisibilityAggregatedCallback = callback;
}
}

View File

@ -201,7 +201,7 @@ public class InvariantDeviceProfile {
DisplayController.getDefaultDisplay(context).getInfo(),
getPredefinedDeviceProfiles(context, gridName));
Info myInfo = new Info(context, display);
Info myInfo = new Info(display);
DisplayOption myDisplayOption = invDistWeightedInterpolate(
myInfo, getPredefinedDeviceProfiles(context, gridName));

View File

@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
@ -44,6 +45,7 @@ import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.DeadObjectException;
import android.os.Handler;
@ -83,6 +85,7 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -661,6 +664,18 @@ public final class Utilities {
return slop * slop;
}
/**
* Helper method to create a content provider
*/
public static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
return new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
command.accept(uri);
}
};
}
private static class FixedSizeEmptyDrawable extends ColorDrawable {
private final int mSize;

View File

@ -2093,10 +2093,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mTempFXY[1] = y;
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
View hotseat = mLauncher.getHotseat();
return mTempFXY[0] >= hotseat.getLeft() &&
mTempFXY[0] <= hotseat.getRight() &&
mTempFXY[1] >= hotseat.getTop() &&
mTempFXY[1] <= hotseat.getBottom();
return mTempFXY[0] >= hotseat.getLeft()
&& mTempFXY[0] <= hotseat.getRight()
&& mTempFXY[1] >= hotseat.getTop()
&& mTempFXY[1] <= hotseat.getBottom();
}
/**
@ -2393,9 +2393,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
spanY = mDragInfo.spanY;
}
final int container = mLauncher.isHotseatLayout(cellLayout) ?
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
final int container = mLauncher.isHotseatLayout(cellLayout)
? LauncherSettings.Favorites.CONTAINER_HOTSEAT
: LauncherSettings.Favorites.CONTAINER_DESKTOP;
final int screenId = getIdForScreen(cellLayout);
if (!mLauncher.isHotseatLayout(cellLayout)
&& screenId != getScreenIdForPageIndex(mCurrentPage)

View File

@ -16,7 +16,7 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import static com.android.launcher3.allapps.AllAppsGridAdapter.SearchAdapterItem;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@ -57,6 +57,7 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
@ -67,9 +68,7 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.function.IntConsumer;
import com.android.systemui.plugins.shared.SearchTarget;
/**
* The all apps view container.
@ -546,13 +545,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
}
AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
if (focusedItem instanceof AdapterItemWithPayload) {
IntConsumer onSelection =
((AdapterItemWithPayload) focusedItem).getSelectionHandler();
if (onSelection != null) {
onSelection.accept(SearchTargetEvent.QUICK_SELECT);
return true;
}
if (focusedItem instanceof SearchAdapterItem) {
SearchTarget searchTarget = ((SearchAdapterItem) focusedItem).getSearchTarget();
SearchEventTracker.INSTANCE.get(getContext()).quickSelect(searchTarget);
}
if (focusedItem.appInfo != null) {
ItemInfo itemInfo = focusedItem.appInfo;

View File

@ -20,8 +20,6 @@ import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APP
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@ -37,29 +35,22 @@ import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.lifecycle.LiveData;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.slice.Slice;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.HeroSearchResultView;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.views.SearchSliceWrapper;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.List;
import java.util.function.IntConsumer;
/**
* The grid view adapter of all the apps.
@ -100,9 +91,11 @@ public class AllAppsGridAdapter extends
public static final int VIEW_TYPE_SEARCH_SUGGEST = 1 << 13;
public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON;
/**
* ViewHolder for each icon.
@ -192,56 +185,25 @@ public class AllAppsGridAdapter extends
|| viewType == VIEW_TYPE_SEARCH_PEOPLE
|| viewType == VIEW_TYPE_SEARCH_THUMBNAIL
|| viewType == VIEW_TYPE_SEARCH_ICON_ROW
|| viewType == VIEW_TYPE_SEARCH_ICON
|| viewType == VIEW_TYPE_SEARCH_SUGGEST;
}
}
/**
* Extension of AdapterItem that contains an extra payload specific to item
*
* @param <T> Play load Type
*/
public static class AdapterItemWithPayload<T> extends AdapterItem {
private T mPayload;
private String mSearchSessionId;
private AllAppsSearchPlugin mPlugin;
private IntConsumer mSelectionHandler;
public static class SearchAdapterItem extends AdapterItem {
private SearchTarget mSearchTarget;
public AllAppsSearchPlugin getPlugin() {
return mPlugin;
}
public void setPlugin(AllAppsSearchPlugin plugin) {
mPlugin = plugin;
}
public AdapterItemWithPayload(T payload, int type, AllAppsSearchPlugin plugin) {
mPayload = payload;
public SearchAdapterItem(SearchTarget searchTarget, int type) {
mSearchTarget = searchTarget;
viewType = type;
mPlugin = plugin;
}
public void setSelectionHandler(IntConsumer runnable) {
mSelectionHandler = runnable;
public SearchTarget getSearchTarget() {
return mSearchTarget;
}
public void setSearchSessionId(String searchSessionId) {
mSearchSessionId = searchSessionId;
}
public String getSearchSessionId() {
return mSearchSessionId;
}
public IntConsumer getSelectionHandler() {
return mSelectionHandler;
}
public T getPayload() {
return mPayload;
}
}
/**
@ -426,11 +388,8 @@ public class AllAppsGridAdapter extends
R.layout.all_apps_icon, parent, false);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
}
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
return new ViewHolder(icon);
@ -446,6 +405,9 @@ public class AllAppsGridAdapter extends
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
case VIEW_TYPE_SEARCH_ICON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_icon, parent, false));
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
return new ViewHolder(
mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
@ -480,6 +442,10 @@ public class AllAppsGridAdapter extends
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
&& holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
}
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON:
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
@ -487,34 +453,6 @@ public class AllAppsGridAdapter extends
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
icon.applyFromApplicationInfo(info);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
break;
}
//TODO: replace with custom TopHitBubbleTextView with support for both shortcut
// and apps
if (adapterItem instanceof AdapterItemWithPayload) {
AdapterItemWithPayload item = (AdapterItemWithPayload) adapterItem;
item.setSelectionHandler(type -> {
SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
type, item.position, item.getSearchSessionId());
e.bundle = HeroSearchResultView.getAppBundle(info);
if (item.getPlugin() != null) {
item.getPlugin().notifySearchTargetEvent(e);
}
});
icon.setOnClickListener(view -> {
item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
mOnIconClickListener.onClick(view);
});
icon.setOnLongClickListener(view -> {
item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
return mOnIconLongClickListener.onLongClick(view);
});
}
else {
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
}
break;
case VIEW_TYPE_EMPTY_SEARCH:
TextView emptyViewText = (TextView) holder.itemView;
@ -532,39 +470,25 @@ public class AllAppsGridAdapter extends
break;
case VIEW_TYPE_SEARCH_SLICE:
SliceView sliceView = (SliceView) holder.itemView;
AdapterItemWithPayload<Uri> slicePayload =
(AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
sliceView.setOnSliceActionListener((info1, s) -> {
if (slicePayload.getPlugin() != null) {
SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
SearchTarget.ItemType.SETTINGS_SLICE,
SearchTargetEvent.CHILD_SELECT, slicePayload.position,
slicePayload.getSearchSessionId());
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("uri", slicePayload.getPayload());
slicePayload.getPlugin().notifySearchTargetEvent(searchTargetEvent);
}
});
try {
LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher,
slicePayload.getPayload());
liveData.observe((Launcher) mLauncher, sliceView);
sliceView.setTag(liveData);
} catch (Exception ignored) {
}
SearchAdapterItem slicePayload = (SearchAdapterItem) mApps.getAdapterItems().get(
position);
SearchTarget searchTarget = slicePayload.getSearchTarget();
sliceView.setTag(new SearchSliceWrapper(mLauncher, sliceView, searchTarget));
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_HERO_APP:
case VIEW_TYPE_SEARCH_ROW:
case VIEW_TYPE_SEARCH_ICON:
case VIEW_TYPE_SEARCH_ICON_ROW:
case VIEW_TYPE_SEARCH_PEOPLE:
case VIEW_TYPE_SEARCH_THUMBNAIL:
case VIEW_TYPE_SEARCH_SUGGEST:
AdapterItemWithPayload item =
(AdapterItemWithPayload) mApps.getAdapterItems().get(position);
PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
payloadResultView.setup(item);
SearchAdapterItem item =
(SearchAdapterItem) mApps.getAdapterItems().get(position);
SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView;
payloadResultView.applySearchTarget(item.getSearchTarget());
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
@ -576,17 +500,15 @@ public class AllAppsGridAdapter extends
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
if (holder.itemView instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.setOnClickListener(null);
icon.setOnLongClickListener(null);
} else if (holder.itemView instanceof SliceView) {
SliceView sliceView = (SliceView) holder.itemView;
sliceView.setOnSliceActionListener(null);
if (sliceView.getTag() instanceof LiveData) {
LiveData sliceLiveData = (LiveData) sliceView.getTag();
sliceLiveData.removeObservers((Launcher) mLauncher);
if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
}
if (holder.itemView instanceof SliceView) {
SliceView sliceView = (SliceView) holder.itemView;
if (sliceView.getTag() instanceof SearchSliceWrapper) {
((SearchSliceWrapper) sliceView.getTag()).destroy();
}
sliceView.setTag(null);
}
}

View File

@ -20,12 +20,14 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
public AllAppsPagedView(Context context) {
this(context, null);
@ -37,6 +39,10 @@ public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
int topPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0
: context.getResources().getDimensionPixelOffset(
R.dimen.all_apps_header_top_padding);
setPadding(0, topPadding, 0, 0);
}
@Override

View File

@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
@ -45,6 +46,8 @@ import java.util.List;
* A RecyclerView with custom fast scroll support for the all apps view.
*/
public class AllAppsRecyclerView extends BaseRecyclerView {
private static final String TAG = "AllAppsContainerView";
private static final boolean DEBUG = true;
private AlphabeticalAppsList mApps;
private final int mNumAppsPerRow;
@ -131,7 +134,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
mEmptySearchBackground.draw(c);
}
if (DEBUG) {
Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
}
super.onDraw(c);
}

View File

@ -26,6 +26,7 @@ import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.util.Themes;
@ -53,6 +54,9 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
int i = 0;
while (i < itemCount) {
View view = parent.getChildAt(i);
if (view instanceof SelfDecoratingView) {
((SelfDecoratingView) view).removeDecoration();
}
int position = parent.getChildAdapterPosition(view);
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
if (adapterItem.searchSectionInfo != null) {
@ -90,7 +94,10 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
&& mAppsView.getApps().getFocusedChild() != null) {
int index = mAppsView.getApps().getFocusedChildIndex();
if (index >= 0 && index < parent.getChildCount()) {
AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
mAppsView.getActiveRecyclerView().getLayoutManager();
if (layoutManager.findFirstVisibleItemPosition() <= index
&& index < parent.getChildCount()) {
decorationHandler.onFocusDraw(c, parent.getChildAt(index));
}
}
@ -101,8 +108,8 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
* Handles grouping and drawing of items in the same all apps sections.
*/
public static class SectionDecorationHandler {
private static final int FILL_ALPHA = (int) (.3f * 255);
private static final int FOCUS_ALPHA = (int) (.8f * 255);
private static final int FILL_ALPHA = 0;
private static final int FOCUS_ALPHA = (int) (.9f * 255);
protected RectF mBounds = new RectF();
private final boolean mIsFullWidth;
@ -152,6 +159,10 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
if (view == null) {
return;
}
if (view instanceof SelfDecoratingView) {
((SelfDecoratingView) view).decorate(mFocusColor);
return;
}
mPaint.setColor(mFocusColor);
canvas.drawRoundRect(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
@ -165,4 +176,18 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
}
}
/**
* An interface for a view to draw highlight indicator
*/
public interface SelfDecoratingView {
/**
* Removes decorations drawing if focus is acquired by another view
*/
void removeDecoration();
/**
* Draws highlight indicator on view.
*/
void decorate(int focusColor);
}
}

View File

@ -133,7 +133,6 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
* in xml-based animations which also handle updating the appropriate UI.
*
* @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
*
* @see #setState(LauncherState)
* @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation)
*/

View File

@ -178,16 +178,46 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
/**
* Sets results list for search
*/
public boolean setSearchResults(ArrayList<AdapterItem> f) {
if (f == null || mSearchResults != f) {
boolean same = mSearchResults != null && mSearchResults.equals(f);
mSearchResults = f;
public boolean setSearchResults(ArrayList<AdapterItem> results) {
if (results == null || mSearchResults != results) {
boolean same = mSearchResults != null && mSearchResults.equals(results);
mSearchResults = results;
onAppsUpdated();
return !same;
}
return false;
}
public boolean appendSearchResults(ArrayList<AdapterItem> results) {
if (mSearchResults != null && results != null && results.size() > 0) {
updateSearchAdapterItems(results, mSearchResults.size());
refreshRecyclerView();
return true;
}
return false;
}
void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
SearchSectionInfo lastSection = null;
for (int i = 0; i < list.size(); i++) {
AdapterItem adapterItem = list.get(i);
adapterItem.position = offset + i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(adapterItem.position);
}
if (lastSection != null) {
lastSection.setPosEnd(adapterItem.position - 1);
}
lastSection = adapterItem.searchSectionInfo;
}
if (adapterItem.isCountedForAccessibility()) {
mAccessibilityResultsCount++;
}
}
}
/**
* Updates internals when the set of apps are updated.
*/
@ -294,28 +324,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
} else {
List<AppInfo> appInfos = new ArrayList<>();
SearchSectionInfo lastSection = null;
for (int i = 0; i < mSearchResults.size(); i++) {
AdapterItem adapterItem = mSearchResults.get(i);
adapterItem.position = i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(i);
}
if (lastSection != null) {
lastSection.setPosEnd(i - 1);
}
lastSection = adapterItem.searchSectionInfo;
}
if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) {
appInfos.add(adapterItem.appInfo);
}
if (adapterItem.isCountedForAccessibility()) {
mAccessibilityResultsCount++;
}
}
updateSearchAdapterItems(mSearchResults, 0);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
// Append the search market item
if (hasNoFilteredResults()) {

View File

@ -111,8 +111,8 @@ public class FloatingHeaderView extends LinearLayout implements
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mHeaderTopPadding = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
mHeaderTopPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 :
context.getResources().getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
}
@Override
@ -130,6 +130,7 @@ public class FloatingHeaderView extends LinearLayout implements
}
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
setPadding(0, mHeaderTopPadding, 0, 0);
mAllRows = mFixedRows;
}
@ -247,7 +248,9 @@ public class FloatingHeaderView extends LinearLayout implements
public int getMaxTranslation() {
if (mMaxTranslation == 0 && mTabsHidden) {
return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
int paddingOffset = getResources().getDimensionPixelSize(
R.dimen.all_apps_search_bar_bottom_padding);
return FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 : paddingOffset;
} else if (mMaxTranslation > 0 && mTabsHidden) {
return mMaxTranslation + getPaddingTop();
} else {

View File

@ -30,13 +30,11 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
@ -114,7 +112,7 @@ public class AllAppsSearchBarController
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
return true;
@ -196,9 +194,16 @@ public class AllAppsSearchBarController
/**
* Called when the search from primary source is complete.
*
* @param items sorted list of search result adapter items.
* @param items sorted list of search result adapter items
*/
void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
void onSearchResult(String query, ArrayList<AdapterItem> items);
/**
* Called when the search from secondary source is complete.
*
* @param items sorted list of search result adapter items
*/
void onAppendSearchResult(String query, ArrayList<AdapterItem> items);
/**
* Called when the search results should be cleared.
@ -208,50 +213,20 @@ public class AllAppsSearchBarController
/**
* An interface for supporting dynamic search results
*
* @param <T> Type of payload
*/
public interface PayloadResultHandler<T> {
/**
* Updates View using Adapter's payload
*/
default void setup(AdapterItemWithPayload<T> adapterItemWithPayload) {
Object[] targetInfo = getTargetInfo();
if (targetInfo != null) {
targetInfo[0] = adapterItemWithPayload.getSearchSessionId();
targetInfo[1] = adapterItemWithPayload.position;
}
applyAdapterInfo(adapterItemWithPayload);
}
void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
public interface SearchTargetHandler {
/**
* Gets object created by {@link PayloadResultHandler#createTargetInfo()}
* Update view using values from {@link SearchTarget}
*/
Object[] getTargetInfo();
void applySearchTarget(SearchTarget searchTarget);
/**
* Creates a wrapper object to hold searchSessionId and item position
* Handles selection of SearchTarget
*/
default Object[] createTargetInfo() {
return new Object[2];
default void handleSelection(int eventType) {
}
/**
* Generates a SearchTargetEvent object for a PayloadHandlerView
*/
default SearchTargetEvent getSearchTargetEvent(SearchTarget.ItemType itemType,
int eventType) {
Object[] targetInfo = getTargetInfo();
if (targetInfo == null) return null;
String searchSessionId = (String) targetInfo[0];
int position = (int) targetInfo[1];
return new SearchTargetEvent(itemType, eventType,
position, searchSessionId);
}
}

View File

@ -42,7 +42,7 @@ 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;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
@ -173,7 +173,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mApps.setSearchResults(items);
notifyResultChanged();
@ -181,6 +181,14 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
}
@Override
public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mApps.appendSearchResults(items);
notifyResultChanged();
}
}
@Override
public void clearSearchResult() {
if (mApps.setSearchResults(null)) {

View File

@ -16,6 +16,7 @@
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;
@ -47,11 +48,12 @@ public class AppsSearchPipeline implements SearchPipeline {
}
@Override
public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
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, query);
List<AppInfo> matchingResults = getTitleMatchResult(apps.data, input);
callback.accept(getAdapterItems(matchingResults));
}
});

View File

@ -46,8 +46,10 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
@Override
public void doSearch(final String query,
final AllAppsSearchBarController.Callbacks callback) {
mAppsSearchPipeline.performSearch(query,
results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
mAppsSearchPipeline.query(query,
results -> mResultHandler.post(
() -> callback.onSearchResult(query, results)),
null);
}
public static boolean matches(AppInfo info, String query, StringMatcher matcher) {

View File

@ -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.launcher3.allapps.search;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.WeakHashMap;
/**
* A singleton class to track and report search events to search provider
*/
public class SearchEventTracker {
@Nullable
private AllAppsSearchPlugin mPlugin;
private final WeakHashMap<SearchTarget, SearchTargetHandler>
mCallbacks = new WeakHashMap<>();
public static final MainThreadInitializedObject<SearchEventTracker> INSTANCE =
new MainThreadInitializedObject<>(SearchEventTracker::new);
private SearchEventTracker(Context context) {
}
/**
* Returns instance of SearchEventTracker
*/
public static SearchEventTracker getInstance(Context context) {
return SearchEventTracker.INSTANCE.get(context);
}
/**
* Sets current connected plugin for event reporting
*/
public void setPlugin(@Nullable AllAppsSearchPlugin plugin) {
mPlugin = plugin;
}
/**
* Sends SearchTargetEvent to search provider
*/
public void notifySearchTargetEvent(SearchTargetEvent searchTargetEvent) {
if (mPlugin != null) {
UI_HELPER_EXECUTOR.post(() -> mPlugin.notifySearchTargetEvent(searchTargetEvent));
}
}
/**
* Registers a {@link SearchTargetHandler} to handle quick launch for specified SearchTarget.
*/
public void registerWeakHandler(SearchTarget searchTarget, SearchTargetHandler targetHandler) {
mCallbacks.put(searchTarget, targetHandler);
}
/**
* Handles quick select for SearchTarget
*/
public void quickSelect(SearchTarget searchTarget) {
SearchTargetHandler searchTargetHandler = mCallbacks.get(searchTarget);
if (searchTargetHandler != null) {
searchTargetHandler.handleSelection(SearchTargetEvent.QUICK_SELECT);
}
}
/**
* flushes all registered quick select handlers
*/
public void clearHandlers() {
mCallbacks.clear();
}
}

View File

@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps.search;
import android.os.CancellationSignal;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import java.util.ArrayList;
@ -23,10 +25,13 @@ 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 performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
void query(String input,
Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
CancellationSignal cancellationSignal);
}

View File

@ -22,7 +22,7 @@ import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHa
*/
public class SearchSectionInfo {
private String mTitle;
private String mSectionId;
private SectionDecorationHandler mDecorationHandler;
public int getPosStart() {
@ -48,8 +48,8 @@ public class SearchSectionInfo {
this(null);
}
public SearchSectionInfo(String title) {
mTitle = title;
public SearchSectionInfo(String sectionId) {
mSectionId = sectionId;
}
public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@ -62,9 +62,9 @@ public class SearchSectionInfo {
}
/**
* Returns the section's title
* Returns the section's ID
*/
public String getTitle() {
return mTitle == null ? "" : mTitle;
public String getSectionId() {
return mSectionId == null ? "" : mSectionId;
}
}

View File

@ -156,10 +156,14 @@ public final class FeatureFlags {
"ENABLE_DATABASE_RESTORE", true,
"Enable database restore when new restore session is created");
public static final BooleanFlag ENABLE_UNIVERSAL_SMARTSPACE = getDebugFlag(
"ENABLE_UNIVERSAL_SMARTSPACE", false,
public static final BooleanFlag ENABLE_SMARTSPACE_UNIVERSAL = getDebugFlag(
"ENABLE_SMARTSPACE_UNIVERSAL", false,
"Replace Smartspace with a version rendered by System UI.");
public static final BooleanFlag ENABLE_SMARTSPACE_BLUECHIP = getDebugFlag(
"ENABLE_SMARTSPACE_BLUECHIP", false,
"Replace Smartspace with the Bluechip version. Ignored if ENABLE_SMARTSPACE_UNIVERSAL is enabled.");
public static final BooleanFlag ENABLE_SYSTEM_VELOCITY_PROVIDER = getDebugFlag(
"ENABLE_SYSTEM_VELOCITY_PROVIDER", true,
"Use system VelocityTracker's algorithm for motion pause detection.");
@ -178,7 +182,7 @@ public final class FeatureFlags {
"Uses a separate recents activity instead of using the integrated recents+Launcher UI");
public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
"ENABLE_MINIMAL_DEVICE", true,
"ENABLE_MINIMAL_DEVICE", false,
"Allow user to toggle minimal device mode in launcher.");
public static void initialize(Context context) {

View File

@ -53,8 +53,8 @@ import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@ -87,7 +87,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
private Bundle mWidgetOptions;
private boolean mFinishOnPause = false;
private InstantAppResolver mInstantAppResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -101,7 +100,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
mApp = LauncherAppState.getInstance(this);
mIdp = mApp.getInvariantDeviceProfile();
mInstantAppResolver = InstantAppResolver.newInstance(this);
// Use the application context to get the device profile, as in multiwindow-mode, the
// confirmation activity might be rotated.
@ -322,6 +320,8 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
}
private void logCommand(StatsLogManager.EventEnum command) {
getStatsLogManager().logger().log(command);
getStatsLogManager().logger()
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
.log(command);
}
}

View File

@ -50,6 +50,7 @@ import android.util.LongSparseArray;
import android.util.TimingLogger;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
@ -315,6 +316,7 @@ public class LoaderTask implements Runnable {
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
boolean clearDb = false;
try {
@ -402,6 +404,7 @@ public class LoaderTask implements Runnable {
WorkspaceItemInfo info;
LauncherAppWidgetInfo appWidgetInfo;
LauncherAppWidgetProviderInfo widgetProviderInfo;
Intent intent;
String targetPkg;
@ -731,6 +734,19 @@ public class LoaderTask implements Runnable {
+ appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
continue;
}
widgetProviderInfo =
widgetHelper.getLauncherAppWidgetInfo(appWidgetId);
if (widgetProviderInfo != null
&& (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
|| appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
// This can happen when display size changes.
c.markDeleted("Widget removed, min sizes not met: "
+ "span=" + appWidgetInfo.spanX + "x"
+ appWidgetInfo.spanY + " minSpan="
+ widgetProviderInfo.minSpanX + "x"
+ widgetProviderInfo.minSpanY);
continue;
}
if (!c.isOnWorkspaceOrHotseat()) {
c.markDeleted("Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");

View File

@ -32,6 +32,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SH
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
import android.content.ComponentName;
import android.content.ContentValues;
@ -50,10 +51,10 @@ import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
import com.android.launcher3.logger.LauncherAtom.Shortcut;
import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ContentWriter;
import java.util.Optional;
@ -282,9 +283,14 @@ public class ItemInfo {
case ITEM_TYPE_DEEP_SHORTCUT:
itemBuilder
.setShortcut(nullableComponent
.map(component -> LauncherAtom.Shortcut.newBuilder()
.setShortcutName(component.flattenToShortString())
.setShortcutId(ShortcutKey.fromItemInfo(this).getId()))
.map(component -> {
Shortcut.Builder lsb = Shortcut.newBuilder()
.setShortcutName(component.flattenToShortString());
Optional.ofNullable(getIntent())
.map(i -> i.getStringExtra(EXTRA_SHORTCUT_ID))
.ifPresent(lsb::setShortcutId);
return lsb;
})
.orElse(LauncherAtom.Shortcut.newBuilder()));
break;
case ITEM_TYPE_SHORTCUT:

View File

@ -61,4 +61,8 @@ public class RemoteActionItemInfo extends ItemInfoWithIcon {
public boolean shouldStartInLauncher() {
return mShouldStart;
}
public boolean isEscapeHatch() {
return mToken.contains("item_type:[ESCAPE_HATCH]");
}
}

View File

@ -275,7 +275,8 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_type", "RIGHT_EDGE_BACK_NAVIGATION"));
"tutorial_steps",
new String[] {"RIGHT_EDGE_BACK_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchBackTutorialPreference);
@ -284,7 +285,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "HOME_NAVIGATION"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"HOME_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchHomeTutorialPreference);
@ -293,7 +296,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
@ -302,7 +307,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "ASSISTANT"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"ASSISTANT"}));
return true;
});
sandboxCategory.addPreference(launchAssistantTutorialPreference);
@ -311,7 +318,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "SANDBOX_MODE"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"SANDBOX_MODE"}));
return true;
});
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);

View File

@ -21,14 +21,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@ -43,16 +38,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
private final ContentResolver mContentResolver;
private boolean mSystemAutoRotateEnabled;
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updateAutoRotateSetting();
}
};
public static boolean getAllowRotationDefaultValue() {
// If the device's pixel density was scaled (usually via settings for A11y), use the
// original dimensions to determine if rotation is allowed of not.
@ -106,20 +91,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
} else {
mSharedPrefs = null;
}
mContentResolver = activity.getContentResolver();
}
private void updateAutoRotateSetting() {
int autoRotateEnabled = 0;
try {
autoRotateEnabled = Settings.System.getInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "autorotate setting not found", e);
}
mSystemAutoRotateEnabled = autoRotateEnabled == 1;
}
@Override
@ -129,7 +100,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
getAllowRotationDefaultValue());
if (mHomeRotationEnabled != wasRotationEnabled) {
notifyChange();
updateAutoRotateSetting();
}
}
@ -165,11 +135,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
if (!mInitialized) {
mInitialized = true;
notifyChange();
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
false, mSystemAutoRotateObserver);
updateAutoRotateSetting();
}
}
@ -179,7 +144,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
}
}
@ -225,9 +189,8 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
+ " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b,"
+ " mSystemAutoRotateEnabled=%b]",
+ " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled);
mIgnoreAutoRotateSettings, mHomeRotationEnabled);
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.util;
import android.os.Looper;
import androidx.annotation.WorkerThread;
/**
* Utility class to define an object which does most of it's processing on a
* dedicated background thread.
*/
public abstract class BgObjectWithLooper {
/**
* Start initialization of the object
*/
public final void initializeInBackground(String threadName) {
new Thread(this::runOnThread, threadName).start();
}
private void runOnThread() {
Looper.prepare();
onInitialized(Looper.myLooper());
Looper.loop();
}
/**
* Called on the background thread to handle initialization
*/
@WorkerThread
protected abstract void onInitialized(Looper looper);
}

View File

@ -31,6 +31,8 @@ import android.view.Display;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
import java.util.ArrayList;
/**
@ -157,13 +159,13 @@ public class DisplayController implements DisplayListener {
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
private DisplayController.Info mInfo;
private DisplayHolder(Context displayContext) {
private DisplayHolder(Context displayContext, Display display) {
mDisplayContext = displayContext;
// Note that the Display object must be obtained from DisplayManager which is
// associated to the display context, so the Display is isolated from Activity and
// Application to provide the actual state of device that excludes the additional
// adjustment and override.
mInfo = new DisplayController.Info(mDisplayContext);
mInfo = new DisplayController.Info(display);
mId = mInfo.id;
}
@ -180,22 +182,31 @@ public class DisplayController implements DisplayListener {
}
protected void handleOnChange() {
Display display = Utilities.ATLEAST_R
? mDisplayContext.getDisplay()
: mDisplayContext
.getSystemService(DisplayManager.class)
.getDisplay(mId);
if (display == null) {
return;
}
Info oldInfo = mInfo;
Info info = new Info(mDisplayContext);
Info newInfo = new Info(display);
int change = 0;
if (info.hasDifferentSize(oldInfo)) {
if (newInfo.hasDifferentSize(oldInfo)) {
change |= CHANGE_SIZE;
}
if (oldInfo.rotation != info.rotation) {
if (newInfo.rotation != oldInfo.rotation) {
change |= CHANGE_ROTATION;
}
if (info.singleFrameMs != oldInfo.singleFrameMs) {
if (newInfo.singleFrameMs != oldInfo.singleFrameMs) {
change |= CHANGE_FRAME_DELAY;
}
if (change != 0) {
mInfo = info;
mInfo = newInfo;
final int flags = change;
MAIN_EXECUTOR.execute(() -> notifyChange(flags));
}
@ -216,7 +227,7 @@ public class DisplayController implements DisplayListener {
// Use application context to create display context so that it can have its own
// Resources.
Context displayContext = context.getApplicationContext().createDisplayContext(display);
return new DisplayHolder(displayContext);
return new DisplayHolder(displayContext, display);
}
}
@ -244,12 +255,7 @@ public class DisplayController implements DisplayListener {
this.metrics = metrics;
}
private Info(Context context) {
this(context, context.getSystemService(DisplayManager.class)
.getDisplay(DEFAULT_DISPLAY));
}
public Info(Context context, Display display) {
public Info(Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
@ -262,7 +268,8 @@ public class DisplayController implements DisplayListener {
display.getRealSize(realSize);
display.getCurrentSizeRange(smallestSize, largestSize);
metrics = context.getResources().getDisplayMetrics();
metrics = new DisplayMetrics();
display.getMetrics(metrics);
}
private boolean hasDifferentSize(Info info) {

View File

@ -20,10 +20,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.graphics.Point;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
@ -37,8 +35,9 @@ import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
@ -48,24 +47,28 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
/**
* A view representing a high confidence app search result that includes shortcuts
* TODO (sfufa@) consolidate this with SearchResultIconRow
*/
public class HeroSearchResultView extends LinearLayout implements DragSource,
PayloadResultHandler<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> {
public class HeroSearchResultView extends LinearLayout implements DragSource, SearchTargetHandler {
public static final String TARGET_TYPE_HERO_APP = "hero_app";
public static final int MAX_SHORTCUTS_COUNT = 2;
private final Object[] mTargetInfo = createTargetInfo();
BubbleTextView mBubbleTextView;
View mIconView;
BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
AllAppsSearchPlugin mPlugin;
private SearchTarget mSearchTarget;
private BubbleTextView mBubbleTextView;
private View mIconView;
private BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
public HeroSearchResultView(Context context) {
super(context);
@ -96,8 +99,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
launcher.getItemOnClickListener().onClick(view);
});
mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this));
setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, grid.allAppsCellHeightPx));
mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0);
@ -108,35 +109,39 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
grid.allAppsIconSizePx));
bubbleTextView.setOnClickListener(view -> {
WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO,
SearchTargetEvent.CHILD_SELECT);
event.bundle = getAppBundle(itemInfo);
event.bundle.putString("shortcut_id", itemInfo.getDeepShortcutId());
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(event);
}
SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
launcher.getItemOnClickListener().onClick(view);
});
}
}
/**
* Apply {@link ItemInfo} for appIcon and shortcut Icons
*/
@Override
public void applyAdapterInfo(
AdapterItemWithPayload<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> adapterItem) {
mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
AllAppsStore apps = Launcher.getLauncher(getContext()).getAppsView().getAppsStore();
AppInfo appInfo = apps.getApp(new ComponentKey(searchTarget.getComponentName(),
searchTarget.getUserHandle()));
List<ShortcutInfo> infos = mSearchTarget.getShortcutInfos();
ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcuts = new ArrayList<>();
for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) {
ShortcutInfo shortcutInfo = infos.get(i);
ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext());
si.rank = i;
shortcuts.add(new Pair<>(shortcutInfo, si));
}
mBubbleTextView.applyFromApplicationInfo(appInfo);
mIconView.setBackground(mBubbleTextView.getIcon());
mIconView.setTag(adapterItem.appInfo);
List<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcutDetails = adapterItem.getPayload();
mIconView.setTag(appInfo);
LauncherAppState appState = LauncherAppState.getInstance(getContext());
for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
BubbleTextView shortcutView = mDeepShortcutTextViews[i];
mDeepShortcutTextViews[i].setVisibility(shortcutDetails.size() > i ? VISIBLE : GONE);
if (i < shortcutDetails.size()) {
Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcutDetails.get(i);
mDeepShortcutTextViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE);
if (i < shortcuts.size()) {
Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcuts.get(i);
//apply ItemInfo and prepare view
shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second);
MODEL_EXECUTOR.execute(() -> {
@ -146,13 +151,7 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
});
}
}
mPlugin = adapterItem.getPlugin();
adapterItem.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
@Override
@ -190,38 +189,21 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
SearchTargetEvent event = mContainer.getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
event.bundle = getAppBundle(itemInfo);
if (mContainer.mPlugin != null) {
mContainer.mPlugin.notifySearchTargetEvent(event);
}
SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget,
SearchTargetEvent.LONG_PRESS).build();
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event);
return false;
}
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag();
if (itemInfo == null) return;
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO, eventType);
event.bundle = getAppBundle(itemInfo);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(event);
}
}
/**
* Helper method to generate {@link SearchTargetEvent} bundle from {@link ItemInfo}
*/
public static Bundle getAppBundle(ItemInfo itemInfo) {
Bundle b = new Bundle();
b.putParcelable(Intent.EXTRA_COMPONENT_NAME, itemInfo.getTargetComponent());
b.putParcelable(Intent.EXTRA_USER, itemInfo.user);
return b;
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A {@link BubbleTextView} representing a single cell result in AllApps
*/
public class SearchResultIcon extends BubbleTextView implements
AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener,
View.OnLongClickListener {
public static final String TARGET_TYPE_APP = "app";
private final Launcher mLauncher;
private SearchTarget mSearchTarget;
public SearchResultIcon(Context context) {
this(context, null, 0);
}
public SearchResultIcon(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLauncher = Launcher.getLauncher(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setLongPressTimeoutFactor(1f);
setOnFocusChangeListener(mLauncher.getFocusHandler());
setOnClickListener(this);
setOnLongClickListener(this);
getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this);
if (searchTarget.getItemType().equals(TARGET_TYPE_APP)) {
AppInfo appInfo = appsStore.getApp(new ComponentKey(searchTarget.getComponentName(),
searchTarget.getUserHandle()));
applyFromApplicationInfo(appInfo);
}
}
@Override
public void handleSelection(int eventType) {
mLauncher.getItemOnClickListener().onClick(this);
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
@Override
public boolean onLongClick(View view) {
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build());
return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
}
}

View File

@ -22,134 +22,175 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTarget.ItemType;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A view representing a stand alone shortcut search result
*/
public class SearchResultIconRow extends DoubleShadowBubbleTextView implements
AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
AllAppsSearchBarController.SearchTargetHandler {
private final Object[] mTargetInfo = createTargetInfo();
private ShortcutInfo mShortcutInfo;
private AllAppsSearchPlugin mPlugin;
private AdapterItemWithPayload<SearchTarget> mAdapterItem;
public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action";
public static final String TARGET_TYPE_SUGGEST = "suggest";
public static final String TARGET_TYPE_SHORTCUT = "shortcut";
public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
public static final String REMOTE_ACTION_TOKEN = "action_token";
private final int mCustomIconResId;
private final boolean mMatchesInset;
private SearchTarget mSearchTarget;
public SearchResultIconRow(@NonNull Context context) {
super(context);
this(context, null, 0);
}
public SearchResultIconRow(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0);
}
public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SearchResultIconRow, defStyleAttr, 0);
mCustomIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery,
false);
a.recycle();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Launcher launcher = Launcher.getLauncher(getContext());
if (mMatchesInset && launcher.getAppsView() != null && getParent() != null) {
EditText editText = launcher.getAppsView().getSearchUiManager().getEditText();
if (editText != null) {
int counterOffset = getIconSize() + getCompoundDrawablePadding() / 2;
setPadding(editText.getLeft() - counterOffset, getPaddingTop(),
getPaddingRight(), getPaddingBottom());
}
}
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
if (mAdapterItem != null) {
mAdapterItem.setSelectionHandler(null);
protected void drawFocusHighlight(Canvas canvas) {
mHighlightPaint.setColor(mHighlightColor);
float r = Themes.getDialogCornerRadius(getContext());
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), r, r, mHighlightPaint);
}
mAdapterItem = adapterItemWithPayload;
SearchTarget payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
if (payload.mRemoteAction != null) {
prepareUsingRemoteAction(payload.mRemoteAction,
payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
} else {
prepareUsingShortcutInfo(payload.shortcuts.get(0));
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
String type = searchTarget.getItemType();
if (type.equals(TARGET_TYPE_REMOTE_ACTION) || type.equals(TARGET_TYPE_SUGGEST)) {
prepareUsingRemoteAction(searchTarget.getRemoteAction(),
searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN),
searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START),
type.equals(TARGET_TYPE_REMOTE_ACTION));
} else if (type.equals(TARGET_TYPE_SHORTCUT)) {
prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
}
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
applyFromWorkspaceItem(workspaceItemInfo);
LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
if (!loadIconFromResource()) {
MODEL_EXECUTOR.execute(() -> {
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
reapplyItemInfoAsync(workspaceItemInfo);
});
}
}
private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start) {
private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start,
boolean useIconToBadge) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
applyFromRemoteActionInfo(itemInfo);
if (itemInfo.isEscapeHatch() || !loadIconFromResource()) {
UI_HELPER_EXECUTOR.post(() -> {
// If the Drawable from the remote action is not AdaptiveBitmap, styling will not work.
// If the Drawable from the remote action is not AdaptiveBitmap, styling will not
// work.
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
itemInfo.bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
Build.VERSION.SDK_INT);
if (useIconToBadge) {
BitmapInfo placeholder = li.createIconBitmap(
itemInfo.getRemoteAction().getTitle().toString().substring(0, 1),
bitmap.color);
itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap);
} else {
itemInfo.bitmap = bitmap;
}
reapplyItemInfoAsync(itemInfo);
}
});
}
}
private boolean loadIconFromResource() {
if (mCustomIconResId == 0) return false;
setIcon(Launcher.getLauncher(getContext()).getDrawable(mCustomIconResId));
return true;
}
void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon));
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void handleSelection(int eventType) {
public void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) getTag();
Launcher launcher = Launcher.getLauncher(getContext());
final SearchTargetEvent searchTargetEvent;
if (itemInfo instanceof WorkspaceItemInfo) {
ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.SHORTCUT,
eventType);
searchTargetEvent.shortcut = mShortcutInfo;
} else {
RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
searchTargetEvent = getSearchTargetEvent(ItemType.ACTION,
eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.remoteAction = remoteItemInfo.getRemoteAction();
searchTargetEvent.bundle.putBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START,
remoteItemInfo.shouldStartInLauncher());
searchTargetEvent.bundle.putString(SearchTarget.REMOTE_ACTION_TOKEN,
remoteItemInfo.getToken());
}
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}

View File

@ -23,9 +23,12 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageButton;
@ -39,10 +42,10 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@ -52,7 +55,9 @@ import java.util.ArrayList;
* A view representing a single people search result in all apps
*/
public class SearchResultPeopleView extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_PEOPLE = "people";
private final int mIconSize;
private final int mButtonSize;
@ -60,9 +65,10 @@ public class SearchResultPeopleView extends LinearLayout implements
private View mIconView;
private TextView mTitleView;
private ImageButton[] mProviderButtons = new ImageButton[3];
private AllAppsSearchPlugin mPlugin;
private Intent mIntent;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchResultPeopleView(Context context) {
this(context, null, 0);
@ -99,21 +105,19 @@ public class SearchResultPeopleView extends LinearLayout implements
}
@Override
public void applyAdapterInfo(
AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle payload = searchTarget.getExtras();
mTitleView.setText(payload.getString("title"));
mIntent = payload.getParcelable("intent");
Bitmap icon = payload.getParcelable("icon");
if (icon != null) {
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
float radius = Themes.getDialogCornerRadius(getContext());
d.setCornerRadius(radius);
d.setBounds(0, 0, mIconSize, mIconSize);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
Bitmap.createScaledBitmap(icon, mIconSize, mIconSize, false));
mIconView.setBackground(d);
Bitmap contactIcon = payload.getParcelable("icon");
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
BitmapInfo badgeInfo = li.createBadgedIconBitmap(
getAppIcon(mIntent.getPackage()), Process.myUserHandle(),
Build.VERSION.SDK_INT);
setIcon(li.badgeBitmap(roundBitmap(contactIcon), badgeInfo).icon, false);
} catch (Exception e) {
setIcon(contactIcon, true);
}
ArrayList<Bundle> providers = payload.getParcelableArrayList("providers");
@ -122,59 +126,80 @@ public class SearchResultPeopleView extends LinearLayout implements
if (providers != null && i < providers.size()) {
Bundle provider = providers.get(i);
Intent intent = provider.getParcelable("intent");
setupProviderButton(button, provider, intent, adapterItemWithPayload);
String pkg = provider.getString("package_name");
setupProviderButton(button, provider, intent);
UI_HELPER_EXECUTOR.post(() -> {
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
pkg, 0);
Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
String pkg = provider.getString("package_name");
Drawable appIcon = getAppIcon(pkg);
if (appIcon != null) {
MAIN_EXECUTOR.post(() -> button.setImageDrawable(appIcon));
} catch (PackageManager.NameNotFoundException ignored) {
}
});
button.setVisibility(VISIBLE);
} else {
button.setVisibility(GONE);
}
}
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
/**
* Normalizes the bitmap to look like rounded App Icon
* TODO(b/170234747) to support styling, generate adaptive icon drawable and generate
* bitmap from it.
*/
private Bitmap roundBitmap(Bitmap icon) {
final RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
d.setCornerRadius(R.attr.folderIconRadius);
d.setBounds(0, 0, mIconSize, mIconSize);
final Bitmap bitmap = Bitmap.createBitmap(d.getBounds().width(), d.getBounds().height(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
d.draw(canvas);
return bitmap;
}
private void setupProviderButton(ImageButton button, Bundle provider, Intent intent,
AllAppsGridAdapter.AdapterItem adapterItem) {
private void setIcon(Bitmap icon, Boolean round) {
if (round) {
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
d.setCornerRadius(R.attr.folderIconRadius);
d.setBounds(0, 0, mIconSize, mIconSize);
mIconView.setBackground(d);
} else {
mIconView.setBackground(new BitmapDrawable(getResources(), icon));
}
}
private Drawable getAppIcon(String pkg) {
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
pkg, 0);
return applicationInfo.loadIcon(mPackageManager);
} catch (PackageManager.NameNotFoundException ignored) {
return null;
}
}
private void setupProviderButton(ImageButton button, Bundle provider, Intent intent) {
Launcher launcher = Launcher.getLauncher(getContext());
button.setOnClickListener(b -> {
launcher.startActivitySafely(b, intent, null);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.PEOPLE,
SearchTargetEvent.CHILD_SELECT);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
searchTargetEvent.bundle.putBundle("provider", provider);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
Bundle bundle = new Bundle();
bundle.putBundle("provider", provider);
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setExtras(bundle).build());
});
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mIntent != null) {
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivitySafely(this, mIntent, null);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.PEOPLE,
eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
}

View File

@ -21,6 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
@ -36,20 +41,28 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
/**
* A View representing a PlayStore item.
*/
public class SearchResultPlayItem extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_PLAY = "play";
private static final int BITMAP_CROP_MASK_COLOR = 0xff424242;
final Paint mIconPaint = new Paint();
final Rect mTempRect = new Rect();
private final DeviceProfile mDeviceProfile;
private View mIconView;
private TextView mTitleView;
@ -57,8 +70,8 @@ public class SearchResultPlayItem extends LinearLayout implements
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
private AllAppsSearchPlugin mPlugin;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchResultPlayItem(Context context) {
@ -91,14 +104,35 @@ public class SearchResultPlayItem extends LinearLayout implements
iconParams.height = mDeviceProfile.allAppsIconSizePx;
iconParams.width = mDeviceProfile.allAppsIconSizePx;
setOnClickListener(view -> handleSelection(SearchTargetEvent.SELECT));
}
private Bitmap getRoundedBitmap(Bitmap bitmap) {
final int iconSize = bitmap.getWidth();
final float radius = Themes.getDialogCornerRadius(getContext());
Bitmap output = BitmapRenderer.createHardwareBitmap(iconSize, iconSize, (canvas) -> {
mTempRect.set(0, 0, iconSize, iconSize);
final RectF rectF = new RectF(mTempRect);
mIconPaint.setAntiAlias(true);
mIconPaint.reset();
canvas.drawARGB(0, 0, 0, 0);
mIconPaint.setColor(BITMAP_CROP_MASK_COLOR);
canvas.drawRoundRect(rectF, radius, radius, mIconPaint);
mIconPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, mTempRect, mTempRect, mIconPaint);
});
return output;
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
if (bundle.getString("package", "").equals(mPackageName)) {
return;
}
@ -109,17 +143,19 @@ public class SearchResultPlayItem extends LinearLayout implements
// TODO: Should use a generic type to get values b/165320033
showIfNecessary(mDetailViews[0], bundle.getString("price"));
showIfNecessary(mDetailViews[1], bundle.getString("rating"));
showIfNecessary(mDetailViews[2], bundle.getString("category"));
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
UI_HELPER_EXECUTOR.execute(() -> {
try {
// TODO: Handle caching
URL url = new URL(bundle.getString("icon_url"));
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
URLConnection con = url.openConnection();
// TODO: monitor memory and investigate if it's better to use glide
con.addRequestProperty("Cache-Control", "max-age: 0");
con.setUseCaches(true);
Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), getRoundedBitmap(
Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
mDeviceProfile.allAppsIconSizePx, false));
mDeviceProfile.allAppsIconSizePx, false)));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
@ -127,11 +163,6 @@ public class SearchResultPlayItem extends LinearLayout implements
});
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
@ -141,7 +172,8 @@ public class SearchResultPlayItem extends LinearLayout implements
}
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mPackageName == null) return;
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
"https://play.google.com/store/apps/details?id="
@ -167,12 +199,7 @@ public class SearchResultPlayItem extends LinearLayout implements
}
private void logSearchEvent(int eventType) {
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.PLAY_RESULTS, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putString("package_name", mPackageName);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}

View File

@ -1,131 +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.views;
import static com.android.systemui.plugins.shared.SearchTarget.ItemType.SUGGEST;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A view representing a fallback search suggestion row.
*/
public class SearchResultSuggestRow extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
private final Object[] mTargetInfo = createTargetInfo();
private AllAppsSearchPlugin mPlugin;
private AdapterItemWithPayload<SearchTarget> mAdapterItem;
private TextView mTitle;
public SearchResultSuggestRow(@NonNull Context context) {
super(context);
}
public SearchResultSuggestRow(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchResultSuggestRow(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitle = findViewById(R.id.title);
setOnClickListener(this);
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
mAdapterItem = adapterItemWithPayload;
SearchTarget payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
if (payload.mRemoteAction != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(payload.mRemoteAction,
payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
setTag(itemInfo);
}
showIfAvailable(mTitle, payload.mRemoteAction.getTitle().toString());
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) getTag();
Launcher launcher = Launcher.getLauncher(getContext());
if (itemInfo instanceof RemoteActionItemInfo) return;
RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SUGGEST, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.remoteAction = remoteItemInfo.getRemoteAction();
searchTargetEvent.bundle.putBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START,
remoteItemInfo.shouldStartInLauncher());
searchTargetEvent.bundle.putString(SearchTarget.REMOTE_ACTION_TOKEN,
remoteItemInfo.getToken());
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
private void showIfAvailable(TextView view, @Nullable String string) {
System.out.println("Plugin suggest string:" + string);
if (TextUtils.isEmpty(string)) {
view.setVisibility(GONE);
} else {
System.out.println("Plugin suggest string:" + string);
view.setVisibility(VISIBLE);
view.setText(string);
}
}
}

View File

@ -21,14 +21,16 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.shared.SearchTarget;
/**
* Header text view that shows a title for a given section in All apps search
*/
public class SearchSectionHeaderView extends TextView implements
AllAppsSearchBarController.PayloadResultHandler<String> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_SECTION_HEADER = "section_header";
public SearchSectionHeaderView(Context context) {
super(context);
}
@ -43,8 +45,8 @@ public class SearchSectionHeaderView extends TextView implements
}
@Override
public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
String title = adapterItem.getPayload();
public void applySearchTarget(SearchTarget searchTarget) {
String title = searchTarget.getExtras().getString("title");
if (title == null || !title.isEmpty()) {
setText(title);
setVisibility(VISIBLE);
@ -52,9 +54,4 @@ public class SearchSectionHeaderView extends TextView implements
setVisibility(INVISIBLE);
}
}
@Override
public Object[] getTargetInfo() {
return null;
}
}

View File

@ -29,9 +29,8 @@ import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@ -41,14 +40,16 @@ import java.util.ArrayList;
* A row of tappable TextViews with a breadcrumb for settings search.
*/
public class SearchSettingsRowView extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mBreadcrumbsView;
private Intent mIntent;
private AllAppsSearchPlugin mPlugin;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchSettingsRowView(@NonNull Context context) {
@ -75,10 +76,9 @@ public class SearchSettingsRowView extends LinearLayout implements
}
@Override
public void applyAdapterInfo(
AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
mIntent = bundle.getParcelable("intent");
showIfAvailable(mTitleView, bundle.getString("title"));
showIfAvailable(mDescriptionView, bundle.getString("description"));
@ -86,12 +86,7 @@ public class SearchSettingsRowView extends LinearLayout implements
//TODO: implement RTL friendly breadcrumbs view
showIfAvailable(mBreadcrumbsView, breadcrumbs != null
? String.join(" > ", breadcrumbs) : null);
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
private void showIfAvailable(TextView view, @Nullable String string) {
@ -108,19 +103,15 @@ public class SearchSettingsRowView extends LinearLayout implements
handleSelection(SearchTargetEvent.SELECT);
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mIntent == null) return;
// TODO: create ItemInfo object and then use it to call startActivityForResult for proper
// WW logging
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivityForResult(mIntent, 0);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.SETTINGS_ROW, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.views;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.widget.EventInfo;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A Wrapper class for {@link SliceView} search results
*/
public class SearchSliceWrapper implements SliceView.OnSliceActionListener {
public static final String TARGET_TYPE_SLICE = "settings_slice";
private static final String TAG = "SearchSliceController";
private static final String URI_EXTRA_KEY = "slice_uri";
private final Launcher mLauncher;
private final SearchTarget mSearchTarget;
private final SliceView mSliceView;
private LiveData<Slice> mSliceLiveData;
public SearchSliceWrapper(Context context, SliceView sliceView, SearchTarget searchTarget) {
mLauncher = Launcher.getLauncher(context);
mSearchTarget = searchTarget;
mSliceView = sliceView;
sliceView.setOnSliceActionListener(this);
try {
mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri());
mSliceLiveData.observe((Launcher) mLauncher, sliceView);
} catch (Exception ex) {
Log.e(TAG, "unable to bind slice", ex);
}
}
/**
* Unregisters event handlers and removes lifecycle observer
*/
public void destroy() {
mSliceView.setOnSliceActionListener(null);
mSliceLiveData.removeObservers(mLauncher);
}
@Override
public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).build());
}
private Uri getSliceUri() {
return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
}
}

View File

@ -15,6 +15,9 @@
*/
package com.android.launcher3.views;
import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_SHOULD_START;
import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_TOKEN;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@ -26,14 +29,13 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@ -41,11 +43,12 @@ import com.android.systemui.plugins.shared.SearchTargetEvent;
* A view representing a high confidence app search result that includes shortcuts
*/
public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppCompatImageView
implements AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
implements AllAppsSearchBarController.SearchTargetHandler {
private final Object[] mTargetInfo = createTargetInfo();
AllAppsSearchPlugin mPlugin;
int mPosition;
public static final String TARGET_TYPE_SCREENSHOT = "screenshot";
public static final String TARGET_TYPE_SCREENSHOT_LEGACY = "screenshot_legacy";
private SearchTarget mSearchTarget;
public ThumbnailSearchResultView(Context context) {
super(context);
@ -59,7 +62,8 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
super(context, attrs, defStyleAttr);
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
Launcher launcher = Launcher.getLauncher(getContext());
ItemInfo itemInfo = (ItemInfo) getTag();
if (itemInfo instanceof RemoteActionItemInfo) {
@ -68,33 +72,29 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
} else {
ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
}
if (mPlugin != null) {
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.SCREENSHOT, eventType);
mPlugin.notifySearchTargetEvent(event);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItem) {
Launcher launcher = Launcher.getLauncher(getContext());
mPosition = adapterItem.position;
SearchTarget target = adapterItem.getPayload();
public void applySearchTarget(SearchTarget target) {
mSearchTarget = target;
Bitmap bitmap;
if (target.mRemoteAction != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.mRemoteAction,
target.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
target.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
ItemClickHandler.onClickRemoteAction(launcher, itemInfo);
bitmap = ((BitmapDrawable) target.mRemoteAction.getIcon()
if (target.getRemoteAction() != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.getRemoteAction(),
target.getExtras().getString(REMOTE_ACTION_TOKEN),
target.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START));
bitmap = ((BitmapDrawable) target.getRemoteAction().getIcon()
.loadDrawable(getContext())).getBitmap();
setTag(itemInfo);
// crop
bitmap = Bitmap.createBitmap(bitmap, 0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(), bitmap.getWidth());
} else {
bitmap = (Bitmap) target.bundle.getParcelable("bitmap");
bitmap = (Bitmap) target.getExtras().getParcelable("bitmap");
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
itemInfo.intent = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(target.bundle.getString("uri")))
.setData(Uri.parse(target.getExtras().getString("uri")))
.setType("image/*")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
setTag(itemInfo);
@ -103,12 +103,6 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
setImageDrawable(drawable);
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
mPlugin = adapterItem.getPlugin();
adapterItem.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(target, this);
}
}

View File

@ -17,6 +17,8 @@
package com.android.systemui.plugins;
import android.app.Activity;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.view.View;
import com.android.systemui.plugins.annotations.ProvidesInterface;
@ -32,7 +34,7 @@ import java.util.function.Consumer;
@ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
public interface AllAppsSearchPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
int VERSION = 7;
int VERSION = 8;
void setup(Activity activity, View view);
@ -49,10 +51,21 @@ public interface AllAppsSearchPlugin extends Plugin {
void onWindowVisibilityChanged(int visibility);
/**
* Send signal when user starts typing, perform search, when search ends
* Send signal when user starts typing, perform search, notify search target
* event when search ends.
*/
void startedSearchSession();
void performSearch(String query, Consumer<List<SearchTarget>> results);
/**
* Main function that triggers search.
*
* @param input string that has been typed by a user
* @param inputArgs extra info that may be relevant for the input query
* @param results contains the result that will be rendered in all apps search surface
* @param cancellationSignal {@link CancellationSignal} can be used to share status of current
*/
void query(String input, Bundle inputArgs, Consumer<List<SearchTarget>> results,
CancellationSignal cancellationSignal);
/**
* Send over search target interaction events to Plugin

View File

@ -16,8 +16,10 @@
package com.android.systemui.plugins.shared;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
import java.util.List;
@ -26,139 +28,145 @@ import java.util.List;
*/
public class SearchTarget implements Comparable<SearchTarget> {
private final String mItemId;
private final String mItemType;
private final float mScore;
/**
* A bundle key for boolean value of whether remote action should be started in launcher or not
*/
public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
public static final String REMOTE_ACTION_TOKEN = "action_token";
private final ComponentName mComponentName;
private final UserHandle mUserHandle;
private final List<ShortcutInfo> mShortcutInfos;
//TODO: (sfufa) replace with a list of a custom type
private final RemoteAction mRemoteAction;
private final Bundle mExtras;
public enum ViewType {
/**
* Consists of N number of icons. (N: launcher column count)
*/
TOP_HIT(0),
/**
* Consists of 1 icon and two subsidiary icons.
*/
HERO(1),
/**
* Main/sub/breadcrumb texts are rendered.
*/
DETAIL(2),
/**
* Consists of an icon, three detail strings.
*/
ROW(3),
/**
* Consists of an icon, three detail strings and a button.
*/
ROW_WITH_BUTTON(4),
/**
* Consists of a single slice view
*/
SLICE(5),
/**
* Similar to hero section.
*/
SHORTCUT(6),
/**
* Person icon and handling app icons are rendered.
*/
PEOPLE(7),
/**
* N number of 1x1 ratio thumbnail is rendered.
* (current N = 3)
*/
THUMBNAIL(8),
/**
* Fallback search icon and relevant text is rendered.
*/
SUGGEST(9);
private final int mId;
ViewType(int id) {
mId = id;
private SearchTarget(String itemId, String itemType, float score,
ComponentName componentName, UserHandle userHandle, List<ShortcutInfo> shortcutInfos,
RemoteAction remoteAction, Bundle extras) {
mItemId = itemId;
mItemType = itemType;
mScore = score;
mComponentName = componentName;
mUserHandle = userHandle;
mShortcutInfos = shortcutInfos;
mExtras = extras;
mRemoteAction = remoteAction;
}
public int get() {
return mId;
}
public String getItemId() {
return mItemId;
}
public enum ItemType {
PLAY_RESULTS(0, "Play Store", ViewType.DETAIL),
SETTINGS_ROW(1, "Settings", ViewType.ROW),
SETTINGS_SLICE(2, "Settings", ViewType.SLICE),
APP(3, "", ViewType.TOP_HIT),
APP_HERO(4, "", ViewType.HERO),
SHORTCUT(5, "Shortcuts", ViewType.SHORTCUT),
PEOPLE(6, "People", ViewType.PEOPLE),
SCREENSHOT(7, "Screenshots", ViewType.THUMBNAIL),
ACTION(8, "Actions", ViewType.SHORTCUT),
SUGGEST(9, "Fallback Search", ViewType.SUGGEST),
CHROME_TAB(10, "Chrome Tab", ViewType.SHORTCUT);
private final int mId;
/** Used to render section title. */
private final String mTitle;
private final ViewType mViewType;
ItemType(int id, String title, ViewType type) {
mId = id;
mTitle = title;
mViewType = type;
public String getItemType() {
return mItemType;
}
public ViewType getViewType() {
return mViewType;
public ComponentName getComponentName() {
return mComponentName;
}
public String getTitle() {
return mTitle;
public UserHandle getUserHandle() {
return mUserHandle;
}
public int getId() {
return mId;
}
public float getScore() {
return mScore;
}
public ItemType type;
public List<ShortcutInfo> shortcuts;
public Bundle bundle;
public float score;
public String mSessionId;
public RemoteAction mRemoteAction;
public List<ShortcutInfo> getShortcutInfos() {
return mShortcutInfos;
}
/**
* Constructor to create the search target. Bundle is currently temporary to hold
* search target primitives that cannot be expressed as java primitive objects
* or AOSP native objects.
*/
public SearchTarget(ItemType itemType, List<ShortcutInfo> shortcuts,
Bundle bundle, float score, String sessionId) {
this.type = itemType;
this.shortcuts = shortcuts;
this.bundle = bundle;
this.score = score;
this.mSessionId = sessionId;
public Bundle getExtras() {
return mExtras;
}
public RemoteAction getRemoteAction() {
return mRemoteAction;
}
@Override
public int compareTo(SearchTarget o) {
return Float.compare(o.score, score);
return Float.compare(o.mScore, mScore);
}
/**
* A builder for {@link SearchTarget}
*/
public static final class Builder {
private String mItemId;
private final String mItemType;
private final float mScore;
private ComponentName mComponentName;
private UserHandle mUserHandle;
private List<ShortcutInfo> mShortcutInfos;
private Bundle mExtras;
private RemoteAction mRemoteAction;
public Builder(String itemType, float score) {
this(itemType, score, null, null);
}
public Builder(String itemType, float score, ComponentName cn,
UserHandle user) {
mItemType = itemType;
mScore = score;
mComponentName = cn;
mUserHandle = user;
}
public String getItemId() {
return mItemId;
}
public float getScore() {
return mScore;
}
public Builder setItemId(String itemId) {
mItemId = itemId;
return this;
}
public Builder setComponentName(ComponentName componentName) {
mComponentName = componentName;
return this;
}
public Builder setUserHandle(UserHandle userHandle) {
mUserHandle = userHandle;
return this;
}
public Builder setShortcutInfos(List<ShortcutInfo> shortcutInfos) {
mShortcutInfos = shortcutInfos;
return this;
}
public Builder setExtras(Bundle extras) {
mExtras = extras;
return this;
}
public Builder setRemoteAction(RemoteAction remoteAction) {
mRemoteAction = remoteAction;
return this;
}
/**
* Builds a {@link SearchTarget}
*/
public SearchTarget build() {
if (mItemId == null) {
throw new IllegalStateException("Item ID is required for building SearchTarget");
}
return new SearchTarget(mItemId, mItemType, mScore, mComponentName, mUserHandle,
mShortcutInfos,
mRemoteAction, mExtras);
}
}
}

View File

@ -15,32 +15,76 @@
*/
package com.android.systemui.plugins.shared;
import android.app.RemoteAction;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
/**
* Event used for the feedback loop to the plugin. (and future aiai)
*/
public class SearchTargetEvent {
public static final int POSITION_NONE = -1;
public static final int SELECT = 0;
public static final int QUICK_SELECT = 1;
public static final int LONG_PRESS = 2;
public static final int CHILD_SELECT = 3;
public SearchTarget.ItemType type;
public ShortcutInfo shortcut;
public RemoteAction remoteAction;
public int eventType;
public Bundle bundle;
public int index;
public String sessionIdentifier;
private final SearchTarget mSearchTarget;
private final int mEventType;
private final int mShortcutPosition;
private final Bundle mExtras;
public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType, int index,
String sessionId) {
this.type = itemType;
this.eventType = eventType;
this.index = index;
this.sessionIdentifier = sessionId;
public SearchTargetEvent(SearchTarget searchTarget, int eventType, int shortcutPosition,
Bundle extras) {
mSearchTarget = searchTarget;
mEventType = eventType;
mShortcutPosition = shortcutPosition;
mExtras = extras;
}
public SearchTarget getSearchTarget() {
return mSearchTarget;
}
public int getShortcutPosition() {
return mShortcutPosition;
}
public int getEventType() {
return mEventType;
}
public Bundle getExtras() {
return mExtras;
}
/**
* A builder for {@link SearchTarget}
*/
public static final class Builder {
private final SearchTarget mSearchTarget;
private final int mEventType;
private int mShortcutPosition = POSITION_NONE;
private Bundle mExtras;
public Builder(SearchTarget searchTarget, int eventType) {
mSearchTarget = searchTarget;
mEventType = eventType;
}
public Builder setShortcutPosition(int shortcutPosition) {
mShortcutPosition = shortcutPosition;
return this;
}
public Builder setExtras(Bundle extras) {
mExtras = extras;
return this;
}
public SearchTargetEvent build() {
return new SearchTargetEvent(mSearchTarget, mEventType, mShortcutPosition, mExtras);
}
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:minWidth="1dp"
android:minHeight="1dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/test_layout_appwidget_blue"
android:resizeMode="horizontal|vertical"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:minWidth="1dp"
android:minHeight="1dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/test_layout_appwidget_red"
android:resizeMode="horizontal|vertical"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:minWidth="1dp"
android:minHeight="1dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/test_layout_appwidget_blue"
android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"

View File

@ -88,10 +88,6 @@ public class Background extends LauncherInstrumentation.VisibleContainer {
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
: LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
// b/156044202
mLauncher.log("Hierarchy before swiping up to overview:");
mLauncher.dumpViewHierarchy();
mLauncher.sendPointer(
downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
mLauncher.executeAndWaitForLauncherEvent(