Merge "Add brianji@ to OWNERS." into tm-dev
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 3a4bb10..3b4a28b 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -23,8 +23,8 @@
</string-array>
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
-
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
+ <string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 8fb085d..6f0f993 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
-import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS;
import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -38,7 +37,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
-import android.graphics.Insets;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
@@ -46,7 +44,6 @@
import android.os.IBinder;
import android.view.Display;
import android.view.View;
-import android.view.WindowInsets;
import android.window.SplashScreen;
import androidx.annotation.Nullable;
@@ -295,8 +292,8 @@
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
- getStateManager(), getDepthController());
+ new SplitSelectStateController(this, mHandler, getStateManager(),
+ getDepthController());
overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
@@ -614,17 +611,4 @@
mDepthController.dump(prefix, writer);
}
}
-
- @Override
- public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder,
- WindowInsets oldInsets) {
- // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
- // would count towards it). This is used for the bottom protection in All Apps for example.
- if (DisplayController.getNavigationMode(this) == NO_BUTTON) {
- Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
- Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
- oldTappableInsets.right, 0);
- updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
- }
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 6bc4c0a..473be9e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -250,7 +250,11 @@
* Returns whether the taskbar is currently visible and in an app.
*/
public boolean isInAppAndNotStashed() {
- return !mIsStashed && (mState & FLAG_IN_APP) != 0;
+ return !mIsStashed && isInApp();
+ }
+
+ private boolean isInApp() {
+ return hasAnyFlag(FLAG_IN_APP);
}
/**
@@ -266,8 +270,15 @@
return mUnstashedHeight;
}
boolean isAnimating = mAnimator != null && mAnimator.isStarted();
- return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
- ? mStashedHeight : 0;
+ if (!mControllers.stashedHandleViewController.isStashedHandleVisible()
+ && isInApp()
+ && !isAnimating) {
+ // We are in a settled state where we're not showing the handle even though taskbar
+ // is stashed. This can happen for example when home button is disabled (see
+ // StashedHandleViewController#setIsHomeButtonDisabled()).
+ return 0;
+ }
+ return mStashedHeight;
}
return mUnstashedHeight;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
index b36b9f1..37cd753 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -25,6 +25,9 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.allapps.BaseAdapterProvider;
+import com.android.launcher3.allapps.BaseAllAppsAdapter;
import com.android.launcher3.allapps.BaseAllAppsContainerView;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
@@ -79,4 +82,11 @@
setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
return super.onApplyWindowInsets(insets);
}
+
+ @Override
+ protected BaseAllAppsAdapter getAdapter(AlphabeticalAppsList<TaskbarAllAppsContext> mAppsList,
+ BaseAdapterProvider[] adapterProviders) {
+ return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), mAppsList,
+ adapterProviders);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 9240fb8..2f8e4d9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
-import android.view.Display;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -37,20 +36,6 @@
}
/**
- * Returns true if the display is an internal displays
- */
- public static boolean isInternalDisplay(Display display) {
- return display.getType() == Display.TYPE_INTERNAL;
- }
-
- /**
- * Returns a unique ID representing the display
- */
- public static String getUniqueId(Display display) {
- return display.getUniqueId();
- }
-
- /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 32ce1c4..947d3e2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -118,8 +118,8 @@
if (isSplitSelectionState(currentState, toState)) {
// Animation to "dismiss" selected taskView
- PendingAnimation splitSelectInitAnimation =
- mRecentsView.createSplitSelectInitAnimation();
+ PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation(
+ toState.getTransitionDuration(mLauncher));
// Add properties to shift remaining taskViews to get out of placeholder view
splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
toState.getSplitSelectTranslation(mLauncher), LINEAR);
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index e5c7b0e..895cf89 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -35,11 +35,11 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.DisplayController.NavigationMode;
+import com.android.launcher3.util.window.CachedDisplayInfo;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
/**
* Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
@@ -51,55 +51,17 @@
*/
class OrientationTouchTransformer {
- private static class CurrentDisplay {
- public Point size;
- public int rotation;
-
- CurrentDisplay() {
- this.size = new Point(0, 0);
- this.rotation = 0;
- }
-
- CurrentDisplay(Point size, int rotation) {
- this.size = size;
- this.rotation = rotation;
- }
-
- @Override
- public String toString() {
- return "CurrentDisplay:"
- + " rotation: " + rotation
- + " size: " + size;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- CurrentDisplay display = (CurrentDisplay) o;
- if (rotation != display.rotation) return false;
-
- return Objects.equals(size, display.size);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(size, rotation);
- }
- };
-
private static final String TAG = "OrientationTouchTransformer";
private static final boolean DEBUG = false;
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
- private final Map<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
- new HashMap<CurrentDisplay, OrientationRectF>();
+ private final Map<CachedDisplayInfo, OrientationRectF> mSwipeTouchRegions =
+ new HashMap<CachedDisplayInfo, OrientationRectF>();
private final RectF mAssistantLeftRegion = new RectF();
private final RectF mAssistantRightRegion = new RectF();
private final RectF mOneHandedModeRegion = new RectF();
- private CurrentDisplay mCurrentDisplay = new CurrentDisplay();
+ private CachedDisplayInfo mCachedDisplayInfo = new CachedDisplayInfo();
private int mNavBarGesturalHeight;
private final int mNavBarLargerGesturalHeight;
private boolean mEnableMultipleRegions;
@@ -184,22 +146,22 @@
* @see #enableMultipleRegions(boolean, Info)
*/
void createOrAddTouchRegion(Info info) {
- mCurrentDisplay = new CurrentDisplay(info.currentSize, info.rotation);
+ mCachedDisplayInfo = new CachedDisplayInfo(info.currentSize, info.rotation);
if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
- && mCurrentDisplay.rotation == mQuickStepStartingRotation) {
+ && mCachedDisplayInfo.rotation == mQuickStepStartingRotation) {
// User already was swiping and the current screen is same rotation as the starting one
// Remove active nav bars in other rotations except for the one we started out in
resetSwipeRegions(info);
return;
}
- OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplay);
+ OrientationRectF region = mSwipeTouchRegions.get(mCachedDisplayInfo);
if (region != null) {
return;
}
if (mEnableMultipleRegions) {
- mSwipeTouchRegions.put(mCurrentDisplay, createRegionForDisplay(info));
+ mSwipeTouchRegions.put(mCachedDisplayInfo, createRegionForDisplay(info));
} else {
resetSwipeRegions(info);
}
@@ -245,31 +207,31 @@
*/
private void resetSwipeRegions(Info region) {
if (DEBUG) {
- Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplay.rotation);
+ Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation);
}
- mCurrentDisplay = new CurrentDisplay(region.currentSize, region.rotation);
- OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
+ mCachedDisplayInfo = new CachedDisplayInfo(region.currentSize, region.rotation);
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo);
if (regionToKeep == null) {
regionToKeep = createRegionForDisplay(region);
}
mSwipeTouchRegions.clear();
- mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
+ mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
updateAssistantRegions(regionToKeep);
}
private void resetSwipeRegions() {
- OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo);
mSwipeTouchRegions.clear();
if (regionToKeep != null) {
- mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
+ mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
updateAssistantRegions(regionToKeep);
}
}
private OrientationRectF createRegionForDisplay(Info display) {
if (DEBUG) {
- Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation
+ Log.d(TAG, "creating rotation region for: " + mCachedDisplayInfo.rotation
+ " with mode: " + mMode + " displayRotation: " + display.rotation);
}
@@ -368,7 +330,7 @@
true);
}
} else {
- mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation,
+ mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation,
true);
}
break;
@@ -387,7 +349,7 @@
true);
}
} else {
- mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation,
+ mLastRectTouched.applyTransformFromRotation(event, mCachedDisplayInfo.rotation,
true);
}
mLastRectTouched = null;
@@ -403,11 +365,12 @@
if (rect == null) {
continue;
}
- if (rect.applyTransformFromRotation(event, mCurrentDisplay.rotation, false)) {
+ if (rect.applyTransformFromRotation(
+ event, mCachedDisplayInfo.rotation, false)) {
mLastRectTouched = rect;
mActiveTouchRotation = rect.getRotation();
if (mEnableMultipleRegions
- && mCurrentDisplay.rotation == mActiveTouchRotation) {
+ && mCachedDisplayInfo.rotation == mActiveTouchRotation) {
// TODO(b/154580671) might make this block unnecessary
// Start a touch session for the default nav region for the display
mQuickStepStartingRotation = mLastRectTouched.getRotation();
@@ -430,7 +393,7 @@
pw.println(" lastTouchedRegion=" + mLastRectTouched);
pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions);
StringBuilder regions = new StringBuilder(" currentTouchableRotations=");
- for (CurrentDisplay key: mSwipeTouchRegions.keySet()) {
+ for (CachedDisplayInfo key: mSwipeTouchRegions.keySet()) {
OrientationRectF rectF = mSwipeTouchRegions.get(key);
regions.append(rectF).append(" ");
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index db92e33..3e7ad62 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -135,8 +135,8 @@
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
- getStateManager(), null /*depthController*/);
+ new SplitSelectStateController(this, mHandler, getStateManager(),
+ null /* depthController */);
mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView, controller);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index ff175f1..5094d49 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -111,7 +111,8 @@
RecentsState currentState = mActivity.getStateManager().getState();
if (isSplitSelectionState(state) && !isSplitSelectionState(currentState)) {
- setter.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ setter.add(mRecentsView.createSplitSelectInitAnimation(
+ state.getTransitionDuration(mActivity)).buildAnim());
}
Pair<FloatProperty, FloatProperty> taskViewsFloat =
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index d85515a..f6002ec 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -19,6 +19,7 @@
import static androidx.core.util.Preconditions.checkNotNull;
import static androidx.core.util.Preconditions.checkState;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
@@ -82,6 +83,7 @@
public class StatsLogCompatManager extends StatsLogManager {
private static final String TAG = "StatsLog";
+ private static final String LATENCY_TAG = "StatsLatencyLog";
private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
// LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
@@ -196,7 +198,9 @@
private static class StatsCompatLogger implements StatsLogger {
private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
-
+ static {
+ DEFAULT_ITEM_INFO.itemType = ITEM_TYPE_NON_ACTIONABLE;
+ }
private final Context mContext;
private final Optional<ActivityContext> mActivityContext;
private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
@@ -388,13 +392,21 @@
if (IS_VERBOSE) {
String name = (event instanceof Enum) ? ((Enum) event).name() :
event.getId() + "";
-
- Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
- ? String.format("\n%s (State:%s->%s)\n%s", name, getStateString(srcState),
- getStateString(dstState), atomInfo)
- : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name,
- getStateString(srcState), getStateString(dstState), instanceId,
- atomInfo));
+ StringBuilder logStringBuilder = new StringBuilder("\n");
+ if (instanceId != DEFAULT_INSTANCE_ID) {
+ logStringBuilder.append(String.format("InstanceId:%s ", instanceId));
+ }
+ logStringBuilder.append(name);
+ if (srcState != LAUNCHER_STATE_UNSPECIFIED
+ || dstState != LAUNCHER_STATE_UNSPECIFIED) {
+ logStringBuilder.append(
+ String.format("(State:%s->%s)", getStateString(srcState),
+ getStateString(dstState)));
+ }
+ if (mItemInfo != DEFAULT_ITEM_INFO) {
+ logStringBuilder.append("\n").append(atomInfo);
+ }
+ Log.d(TAG, logStringBuilder.toString());
}
for (StatsLogConsumer consumer : LOGS_CONSUMER) {
@@ -479,11 +491,12 @@
if (IS_VERBOSE) {
String name = (event instanceof Enum) ? ((Enum) event).name() :
event.getId() + "";
-
- Log.d(TAG, mInstanceId == DEFAULT_INSTANCE_ID
- ? String.format("\n%s = %dms\n", name, mLatencyInMillis)
- : String.format("\n%s = %dms (InstanceId:%s)\n", name,
- mLatencyInMillis, mInstanceId));
+ StringBuilder logStringBuilder = new StringBuilder("\n");
+ if (mInstanceId != DEFAULT_INSTANCE_ID) {
+ logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
+ }
+ logStringBuilder.append(String.format("%s=%sms", name, mLatencyInMillis));
+ Log.d(LATENCY_TAG, logStringBuilder.toString());
}
SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_LATENCY,
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index e5c656a..1631be0 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -606,7 +606,7 @@
width = Math.min(currentSize.x, currentSize.y);
height = Math.max(currentSize.x, currentSize.y);
}
- return idp.getBestMatch(width, height);
+ return idp.getBestMatch(width, height, mRecentsActivityRotation);
}
private static String nameAndAddress(Object obj) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index fff55a1..21e3ea0 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -17,6 +17,7 @@
package com.android.quickstep.util;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.PendingIntent.FLAG_MUTABLE;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -27,9 +28,11 @@
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
+import android.text.TextUtils;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -45,6 +48,7 @@
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -59,12 +63,13 @@
*/
public class SplitSelectStateController {
+ private final Context mContext;
private final Handler mHandler;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
private final DepthController mDepthController;
private @StagePosition int mStagePosition;
- private PendingIntent mInitialTaskPendingIntent;
+ private Intent mInitialTaskIntent;
private int mInitialTaskId = INVALID_TASK_ID;
private int mSecondTaskId = INVALID_TASK_ID;
private boolean mRecentsAnimationRunning;
@@ -72,10 +77,11 @@
@Nullable
private GroupedTaskView mLaunchingTaskView;
- public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy,
- StateManager stateManager, DepthController depthController) {
+ public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
+ DepthController depthController) {
+ mContext = context;
mHandler = handler;
- mSystemUiProxy = systemUiProxy;
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
mStateManager = stateManager;
mDepthController = depthController;
}
@@ -86,12 +92,11 @@
public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition) {
mInitialTaskId = taskId;
mStagePosition = stagePosition;
- mInitialTaskPendingIntent = null;
+ mInitialTaskIntent = null;
}
- public void setInitialTaskSelect(PendingIntent pendingIntent,
- @StagePosition int stagePosition) {
- mInitialTaskPendingIntent = pendingIntent;
+ public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition) {
+ mInitialTaskIntent = intent;
mStagePosition = stagePosition;
mInitialTaskId = INVALID_TASK_ID;
}
@@ -99,9 +104,22 @@
/**
* To be called after second task selected
*/
- public void setSecondTaskId(int taskId, Consumer<Boolean> callback) {
- mSecondTaskId = taskId;
- launchTasks(mInitialTaskId, mInitialTaskPendingIntent, mSecondTaskId, mStagePosition,
+ public void setSecondTask(Task task, Consumer<Boolean> callback) {
+ mSecondTaskId = task.key.id;
+ final Intent fillInIntent;
+ if (mInitialTaskIntent != null) {
+ fillInIntent = new Intent();
+ if (TextUtils.equals(mInitialTaskIntent.getComponent().getPackageName(),
+ task.topActivity.getPackageName())) {
+ fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ } else {
+ fillInIntent = null;
+ }
+ final PendingIntent pendingIntent =
+ mInitialTaskIntent == null ? null : PendingIntent.getActivity(mContext, 0,
+ mInitialTaskIntent, FLAG_MUTABLE);
+ launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
}
@@ -113,18 +131,32 @@
mLaunchingTaskView = groupedTaskView;
TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
groupedTaskView.getTaskIdAttributeContainers();
- launchTasks(taskIdAttributeContainers[0].getTask().key.id, null,
+ launchTasks(taskIdAttributeContainers[0].getTask().key.id,
taskIdAttributeContainers[1].getTask().key.id,
taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList,
groupedTaskView.getSplitRatio());
}
/**
+ * To be called when we want to launch split pairs from Overview when split is initiated from
+ * Overview.
+ */
+ public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
+ Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+ launchTasks(taskId1, null /* taskPendingIntent */, null /* fillInIntent */, taskId2,
+ stagePosition, callback, freezeTaskList, splitRatio);
+ }
+
+ /**
+ * To be called when we want to launch split pairs from Overview. Split can be initiated from
+ * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
+ * fill in intent with a taskId2 are set.
+ * @param taskPendingIntent is null when split is initiated from Overview
* @param stagePosition representing location of task1
*/
public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
- int taskId2, @StagePosition int stagePosition, Consumer<Boolean> callback,
- boolean freezeTaskList, float splitRatio) {
+ @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
+ Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
// Assume initial task is for top/left part of screen
final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
? new int[]{taskId1, taskId2}
@@ -156,7 +188,7 @@
splitRatio, adapter);
} else {
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
- new Intent(), taskId2, mainOpts.toBundle(), null /* sideOptions */,
+ fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
stagePosition, splitRatio, adapter);
}
}
@@ -250,7 +282,7 @@
*/
public void resetState() {
mInitialTaskId = INVALID_TASK_ID;
- mInitialTaskPendingIntent = null;
+ mInitialTaskIntent = null;
mSecondTaskId = INVALID_TASK_ID;
mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
mRecentsAnimationRunning = false;
@@ -262,7 +294,7 @@
* chosen
*/
public boolean isSplitSelectActive() {
- return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskPendingIntent != null)
+ return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null)
&& mSecondTaskId == INVALID_TASK_ID;
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
new file mode 100644
index 0000000..19a48db
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.content.Context;
+import android.view.Display;
+
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+/**
+ * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher
+ */
+public class SystemWindowManagerProxy extends WindowManagerProxy {
+
+ public SystemWindowManagerProxy(Context context) {
+ super(true);
+ }
+
+ @Override
+ protected String getDisplayId(Display display) {
+ return display.getUniqueId();
+ }
+
+ @Override
+ public boolean isInternalDisplay(Display display) {
+ return display.getType() == Display.TYPE_INTERNAL;
+ }
+
+ @Override
+ public int getRotation(Context context) {
+ return context.getResources().getConfiguration().windowConfiguration.getRotation();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 04af3c1..d9f668d 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -182,9 +182,8 @@
@Override
public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
- getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, null,
- mSecondaryTask.key.id, STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList,
- getSplitRatio());
+ getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, mSecondaryTask.key.id,
+ STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5e331e2..6bb20fc 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -16,7 +16,6 @@
package com.android.quickstep.views;
-import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.view.Surface.ROTATION_0;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
@@ -28,7 +27,6 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
@@ -45,7 +43,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.launcher3.testing.TestProtocol.TASK_VIEW_ID_CRASH;
import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -69,8 +66,6 @@
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
import android.content.LocusId;
import android.content.res.Configuration;
@@ -2013,22 +2008,6 @@
return null;
}
- @Nullable
- private TaskView getTaskViewByComponentName(ComponentName componentName) {
- if (componentName == null) {
- return null;
- }
-
- for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView taskView = requireTaskViewAt(i);
- if (taskView.getItemInfo().getIntent().getComponent().getPackageName().equals(
- componentName.getPackageName())) {
- return taskView;
- }
- }
- return null;
- }
-
public int getRunningTaskIndex() {
TaskView taskView = getRunningTaskView();
return taskView == null ? -1 : indexOfChild(taskView);
@@ -2282,8 +2261,6 @@
* Sets the running task id, cleaning up the old running task if necessary.
*/
public void setCurrentTask(int runningTaskViewId) {
- Log.d(TASK_VIEW_ID_CRASH, "currentRunningTaskViewId: " + mRunningTaskViewId
- + " requestedTaskViewId: " + runningTaskViewId);
if (mRunningTaskViewId == runningTaskViewId) {
return;
}
@@ -3973,28 +3950,17 @@
}
public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
- // Remove the task if it exists in Overview
- TaskView matchingTaskView = getTaskViewByComponentName(
- splitSelectSource.intent.getComponent());
- if (matchingTaskView != null) {
- removeTaskInternal(matchingTaskView.getTaskViewId());
- }
-
mSplitSelectSource = splitSelectSource;
- mSplitSelectStateController.setInitialTaskSelect(
- PendingIntent.getActivity(
- mContext, 0, splitSelectSource.intent, FLAG_MUTABLE),
+ mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition);
}
- public PendingAnimation createSplitSelectInitAnimation() {
+ public PendingAnimation createSplitSelectInitAnimation(int duration) {
if (mSplitHiddenTaskView != null) {
- int duration = mActivity.getStateManager().getState().getTransitionDuration(
- getContext());
return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
true /* dismissingForSplitSelection*/);
} else {
- PendingAnimation anim = new PendingAnimation(SPLIT_LAUNCH_DURATION);
+ PendingAnimation anim = new PendingAnimation(duration);
createInitialSplitSelectAnimation(anim);
return anim;
}
@@ -4039,8 +4005,8 @@
mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isInitialSplit */);
pendingAnimation.addEndListener(aBoolean ->
- mSplitSelectStateController.setSecondTaskId(task.key.id,
- aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
+ mSplitSelectStateController.setSecondTask(
+ task, aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
if (containerTaskView.containsMultipleTasks()) {
// If we are launching from a child task, then only hide the thumbnail itself
mSecondSplitHiddenView = thumbnailView;
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 6dc9f8d..c6cdafc 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -15,16 +15,15 @@
*/
package com.android.quickstep.util;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -38,6 +37,10 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.ReflectionHelpers;
+import com.android.launcher3.util.RotationUtils;
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -142,29 +145,34 @@
LauncherModelHelper helper = new LauncherModelHelper();
try {
helper.sandboxContext.allow(SystemUiProxy.INSTANCE);
+ int rotation = mDisplaySize.x > mDisplaySize.y
+ ? Surface.ROTATION_90 : Surface.ROTATION_0;
+ CachedDisplayInfo cdi =
+ new CachedDisplayInfo("test-display", mDisplaySize, rotation , new Rect());
+ WindowBounds wm = new WindowBounds(
+ new Rect(0, 0, mDisplaySize.x, mDisplaySize.y),
+ mDisplayInsets);
+ WindowBounds[] allBounds = new WindowBounds[4];
+ for (int i = 0; i < 4; i++) {
+ Rect boundsR = new Rect(wm.bounds);
+ Rect insetsR = new Rect(wm.insets);
- Display display = mock(Display.class);
- doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
- doReturn(mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0)
- .when(display).getRotation();
- doAnswer(i -> {
- ((Point) i.getArgument(0)).set(mDisplaySize.x, mDisplaySize.y);
- return null;
- }).when(display).getRealSize(any());
- doAnswer(i -> {
- Point smallestSize = i.getArgument(0);
- Point largestSize = i.getArgument(1);
- smallestSize.x = smallestSize.y = Math.min(mDisplaySize.x, mDisplaySize.y);
- largestSize.x = largestSize.y = Math.max(mDisplaySize.x, mDisplaySize.y);
+ RotationUtils.rotateRect(insetsR, RotationUtils.deltaRotation(rotation, i));
+ RotationUtils.rotateRect(boundsR, RotationUtils.deltaRotation(rotation, i));
+ boundsR.set(0, 0, Math.abs(boundsR.width()), Math.abs(boundsR.height()));
+ allBounds[i] = new WindowBounds(boundsR, insetsR);
+ }
- smallestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
- largestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
+ WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
+ doReturn(cdi).when(wmProxy).getDisplayInfo(any());
+ doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
- smallestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
- largestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
- return null;
- }).when(display).getCurrentSizeRange(any(), any());
- DisplayController.Info mockInfo = new Info(helper.sandboxContext, display);
+ ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache =
+ new ArrayMap<>();
+ perDisplayBoundsCache.put(cdi.id, Pair.create(cdi.normalize(), allBounds));
+
+ DisplayController.Info mockInfo = new Info(
+ helper.sandboxContext, mock(Display.class), wmProxy, perDisplayBoundsCache);
DisplayController controller =
DisplayController.INSTANCE.get(helper.sandboxContext);
@@ -172,7 +180,7 @@
ReflectionHelpers.setField(controller, "mInfo", mockInfo);
mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
- .getBestMatch(mAppBounds.width(), mAppBounds.height());
+ .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation);
mDeviceProfile.updateInsets(mLauncherInsets);
TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext,
diff --git a/res/values/config.xml b/res/values/config.xml
index 1c996eb..509f363 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -69,6 +69,7 @@
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
<string name="model_delegate_class" translatable="false"></string>
+ <string name="window_manager_proxy_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index b47554f..f53d01e 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -29,7 +29,6 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Configuration;
-import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -41,8 +40,6 @@
import android.view.ActionMode;
import android.view.Display;
import android.view.View;
-import android.view.WindowInsets.Type;
-import android.view.WindowMetrics;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -322,11 +319,7 @@
protected WindowBounds getMultiWindowDisplaySize() {
if (Utilities.ATLEAST_R) {
- WindowMetrics wm = getWindowManager().getCurrentWindowMetrics();
-
- Insets insets = wm.getWindowInsets().getInsets(Type.systemBars());
- return new WindowBounds(wm.getBounds(),
- new Rect(insets.left, insets.top, insets.right, insets.bottom));
+ return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
}
// Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
// the app window size
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 84505d9..1b333d8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -75,6 +75,7 @@
public final int heightPx;
public final int availableWidthPx;
public final int availableHeightPx;
+ public final int rotationHint;
public final float aspectRatio;
@@ -239,6 +240,7 @@
this.isGestureMode = isGestureMode;
windowX = windowBounds.bounds.left;
windowY = windowBounds.bounds.top;
+ this.rotationHint = windowBounds.rotationHint;
isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
@@ -548,8 +550,8 @@
}
public Builder toBuilder(Context context) {
- WindowBounds bounds =
- new WindowBounds(widthPx, heightPx, availableWidthPx, availableHeightPx);
+ WindowBounds bounds = new WindowBounds(
+ widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
bounds.bounds.offsetTo(windowX, windowY);
return new Builder(context, inv, mInfo)
.setWindowBounds(bounds)
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 886c657..25fd643 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -55,6 +55,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.WindowManagerProxy;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -180,8 +181,7 @@
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
@VisibleForTesting
- public InvariantDeviceProfile() {
- }
+ public InvariantDeviceProfile() { }
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
@@ -278,11 +278,16 @@
}
private static @DeviceType int getDeviceType(Info displayInfo) {
- // Each screen has two profiles (portrait/landscape), so devices with four or more
- // supported profiles implies two or more internal displays.
- if (displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get()) {
+ int flagPhone = 1 << 0;
+ int flagTablet = 1 << 1;
+
+ int type = displayInfo.supportedBounds.stream()
+ .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
+ .reduce(0, (a, b) -> a | b);
+ if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) {
+ // device has profiles supporting both phone and table modes
return TYPE_MULTI_DISPLAY;
- } else if (displayInfo.supportedBounds.stream().allMatch(displayInfo::isTablet)) {
+ } else if (type == flagTablet) {
return TYPE_TABLET;
} else {
return TYPE_PHONE;
@@ -613,10 +618,14 @@
float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
- return getBestMatch(screenWidth, screenHeight);
+ return getBestMatch(screenWidth, screenHeight,
+ WindowManagerProxy.INSTANCE.get(context).getRotation(context));
}
- public DeviceProfile getBestMatch(float screenWidth, float screenHeight) {
+ /**
+ * Returns the device profile matching the provided screen configuration
+ */
+ public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) {
DeviceProfile bestMatch = supportedProfiles.get(0);
float minDiff = Float.MAX_VALUE;
@@ -626,6 +635,8 @@
if (diff < minDiff) {
minDiff = diff;
bestMatch = profile;
+ } else if (diff == minDiff && profile.rotationHint == rotation) {
+ bestMatch = profile;
}
}
return bestMatch;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5c5aee5..07d7b90 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -228,6 +228,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -1108,21 +1109,23 @@
&& mAllAppsSessionLogId == null) {
// creates new instance ID since new all apps session is started.
mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
- getStatsLogManager().logger().withContainerInfo(
- ContainerInfo.newBuilder().setWorkspace(
- WorkspaceContainer.newBuilder().setPageIndex(
- getWorkspace().getCurrentPage())).build())
- .log(getAllAppsEntryEvent());
+ if (getAllAppsEntryEvent().isPresent()) {
+ getStatsLogManager().logger()
+ .withContainerInfo(ContainerInfo.newBuilder()
+ .setWorkspace(WorkspaceContainer.newBuilder()
+ .setPageIndex(getWorkspace().getCurrentPage())).build())
+ .log(getAllAppsEntryEvent().get());
+ }
}
}
/**
* Returns {@link EventEnum} that should be logged when Launcher enters into AllApps state.
*/
- protected EventEnum getAllAppsEntryEvent() {
- return FeatureFlags.ENABLE_DEVICE_SEARCH.get()
+ protected Optional<EventEnum> getAllAppsEntryEvent() {
+ return Optional.of(FeatureFlags.ENABLE_DEVICE_SEARCH.get()
? LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH
- : LAUNCHER_ALLAPPS_ENTRY;
+ : LAUNCHER_ALLAPPS_ENTRY);
}
@Override
@@ -1151,17 +1154,18 @@
// Making sure mAllAppsSessionLogId is not null to avoid double logging.
&& mAllAppsSessionLogId != null) {
getAppsView().reset(false);
- getStatsLogManager().logger()
- .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
- .setWorkspace(
- LauncherAtom.WorkspaceContainer.newBuilder()
- .setPageIndex(getWorkspace().getCurrentPage()))
- .build())
- .log(LAUNCHER_ALLAPPS_EXIT);
+ getAllAppsExitEvent().ifPresent(getStatsLogManager().logger()::log);
mAllAppsSessionLogId = null;
}
}
+ /**
+ * Returns {@link EventEnum} that should be logged when Launcher exists from AllApps state.
+ */
+ protected Optional<EventEnum> getAllAppsExitEvent() {
+ return Optional.of(LAUNCHER_ALLAPPS_EXIT);
+ }
+
@Override
protected void onResume() {
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT,
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index e3aa758..a5c5c02 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -1,24 +1,19 @@
package com.android.launcher3;
-import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import android.annotation.TargetApi;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewDebug;
import android.view.WindowInsets;
-import androidx.annotation.RequiresApi;
-
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.window.WindowManagerProxy;
import java.util.Collections;
import java.util.List;
@@ -60,76 +55,12 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (Utilities.ATLEAST_R) {
- insets = updateInsetsDueToTaskbar(insets);
- Insets systemWindowInsets = insets.getInsetsIgnoringVisibility(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- mTempRect.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right,
- systemWindowInsets.bottom);
- } else {
- mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
- }
+ insets = WindowManagerProxy.INSTANCE.get(getContext())
+ .normalizeWindowInsets(getContext(), insets, mTempRect);
handleSystemWindowInsets(mTempRect);
return insets;
}
- /**
- * Taskbar provides nav bar and tappable insets. However, taskbar is not attached immediately,
- * and can be destroyed and recreated. Thus, instead of relying on taskbar being present to
- * get its insets, we calculate them ourselves so they are stable regardless of whether taskbar
- * is currently attached.
- *
- * @param oldInsets The system-provided insets, which we are modifying.
- * @return The updated insets.
- */
- @RequiresApi(api = Build.VERSION_CODES.R)
- private WindowInsets updateInsetsDueToTaskbar(WindowInsets oldInsets) {
- if (!ApiWrapper.TASKBAR_DRAWN_IN_PROCESS) {
- // 3P launchers based on Launcher3 should still be inset like normal.
- return oldInsets;
- }
-
- WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
-
- DeviceProfile dp = mActivity.getDeviceProfile();
- Resources resources = getResources();
-
- Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
- Rect newNavInsets = new Rect(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
- oldNavInsets.bottom);
-
- if (dp.isLandscape) {
- boolean isGesturalMode = ResourceUtils.getIntegerByName(
- "config_navBarInteractionMode",
- resources,
- INVALID_RESOURCE_HANDLE) == 2;
- if (dp.isTablet || isGesturalMode) {
- newNavInsets.bottom = dp.isTaskbarPresent
- ? 0
- : ResourceUtils.getNavbarSize("navigation_bar_height_landscape", resources);
- } else {
- int navWidth = ResourceUtils.getNavbarSize("navigation_bar_width", resources);
- if (dp.isSeascape()) {
- newNavInsets.left = navWidth;
- } else {
- newNavInsets.right = navWidth;
- }
- }
- } else {
- newNavInsets.bottom = dp.isTaskbarPresent
- ? 0
- : ResourceUtils.getNavbarSize("navigation_bar_height", resources);
- }
- updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(newNavInsets));
- updatedInsetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(),
- Insets.of(newNavInsets));
-
- mActivity.updateWindowInsets(updatedInsetsBuilder, oldInsets);
-
- return updatedInsetsBuilder.build();
- }
-
@Override
public void setInsets(Rect insets) {
// If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index ece123d..1c36db1 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -28,6 +28,9 @@
public static final String NAVBAR_BOTTOM_GESTURE_LARGER_SIZE =
"navigation_bar_gesture_larger_height";
+ public static final String NAVBAR_HEIGHT = "navigation_bar_height";
+ public static final String NAVBAR_HEIGHT_LANDSCAPE = "navigation_bar_height_landscape";
+
public static int getNavbarSize(String resName, Resources res) {
return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 114f813..b94a612 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -256,4 +256,11 @@
layoutParams.removeRule(RelativeLayout.BELOW);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
}
+
+ @Override
+ protected BaseAllAppsAdapter getAdapter(AlphabeticalAppsList<T> mAppsList,
+ BaseAdapterProvider[] adapterProviders) {
+ return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), mAppsList,
+ adapterProviders);
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index f1ca9c0..58df50c 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -15,36 +15,20 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
-
import android.content.Context;
-import android.content.res.Resources;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.views.ActivityContext;
-import java.util.Arrays;
import java.util.List;
/**
@@ -53,111 +37,26 @@
* @param <T> Type of context inflating all apps.
*/
public class AllAppsGridAdapter<T extends Context & ActivityContext> extends
- RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+ BaseAllAppsAdapter<T> {
public static final String TAG = "AppsGridAdapter";
+ private final GridLayoutManager mGridLayoutMgr;
+ private final GridSpanSizer mGridSizer;
- // A normal icon
- public static final int VIEW_TYPE_ICON = 1 << 1;
- // The message shown when there are no filtered results
- public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2;
- // The message to continue to a market search when there are no filtered results
- public static final int VIEW_TYPE_SEARCH_MARKET = 1 << 3;
-
- // We use various dividers for various purposes. They share enough attributes to reuse layouts,
- // but differ in enough attributes to require different view types
-
- // A divider that separates the apps list and the search market button
- public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
-
- // 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;
-
-
- private final BaseAdapterProvider[] mAdapterProviders;
-
- /**
- * ViewHolder for each icon.
- */
- public static class ViewHolder extends RecyclerView.ViewHolder {
-
- public ViewHolder(View v) {
- super(v);
- }
+ public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
+ AlphabeticalAppsList apps, BaseAdapterProvider[] adapterProviders) {
+ super(activityContext, inflater, apps, adapterProviders);
+ mGridSizer = new GridSpanSizer();
+ mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
+ mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
+ setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
}
/**
- * Info about a particular adapter item (can be either section or app)
+ * Returns the grid layout manager.
*/
- public static class AdapterItem {
- /** Common properties */
- // The index of this adapter item in the list
- public int position;
- // The type of this item
- public int viewType;
-
- // The section name of this item. Note that there can be multiple items with different
- // sectionNames in the same section
- public String sectionName = null;
- // The row that this item shows up on
- public int rowIndex;
- // The index of this app in the row
- public int rowAppIndex;
- // The associated ItemInfoWithIcon for the item
- public ItemInfoWithIcon itemInfo = null;
- // The index of this app not including sections
- public int appIndex = -1;
- // Search section associated to result
- public DecorationInfo decorationInfo = null;
-
- /**
- * Factory method for AppIcon AdapterItem
- */
- public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
- int appIndex) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ICON;
- item.position = pos;
- item.sectionName = sectionName;
- item.itemInfo = appInfo;
- item.appIndex = appIndex;
- return item;
- }
-
- /**
- * Factory method for empty search results view
- */
- public static AdapterItem asEmptySearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_EMPTY_SEARCH;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a dividerView in AllAppsSearch
- */
- public static AdapterItem asAllAppsDivider(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a market search button
- */
- public static AdapterItem asMarketSearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_SEARCH_MARKET;
- item.position = pos;
- return item;
- }
-
- protected boolean isCountedForAccessibility() {
- return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
- }
+ public RecyclerView.LayoutManager getLayoutManager() {
+ return mGridLayoutMgr;
}
/**
@@ -217,7 +116,7 @@
*/
private int getRowsNotForAccessibility(int adapterPosition) {
List<AdapterItem> items = mApps.getAdapterItems();
- adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
+ adapterPosition = Math.max(adapterPosition, items.size() - 1);
int extraRows = 0;
for (int i = 0; i <= adapterPosition; i++) {
if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) {
@@ -228,6 +127,20 @@
}
}
+ @Override
+ public void setAppsPerRow(int appsPerRow) {
+ mAppsPerRow = appsPerRow;
+ int totalSpans = mAppsPerRow;
+ for (BaseAdapterProvider adapterProvider : mAdapterProviders) {
+ for (int itemPerRow : adapterProvider.getSupportedItemsPerRowArray()) {
+ if (totalSpans % itemPerRow != 0) {
+ totalSpans *= itemPerRow;
+ }
+ }
+ }
+ mGridLayoutMgr.setSpanCount(totalSpans);
+ }
+
/**
* Helper class to size the grid items.
*/
@@ -255,202 +168,4 @@
}
}
}
-
- private final T mActivityContext;
- private final LayoutInflater mLayoutInflater;
- private final AlphabeticalAppsList<T> mApps;
- private final GridLayoutManager mGridLayoutMgr;
- private final GridSpanSizer mGridSizer;
-
- private final OnClickListener mOnIconClickListener;
- private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
-
- private int mAppsPerRow;
-
- private OnFocusChangeListener mIconFocusListener;
-
- // The text to show when there are no search results and no market search handler.
- protected String mEmptySearchMessage;
- // The click listener to send off to the market app, updated each time the search query changes.
- private OnClickListener mMarketSearchClickListener;
-
- private final int mExtraHeight;
-
- public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
- AlphabeticalAppsList<T> apps, BaseAdapterProvider[] adapterProviders) {
- Resources res = activityContext.getResources();
- mActivityContext = activityContext;
- mApps = apps;
- mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
- mGridSizer = new GridSpanSizer();
- mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
- mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
- mLayoutInflater = inflater;
-
- mOnIconClickListener = mActivityContext.getItemOnClickListener();
-
- mAdapterProviders = adapterProviders;
- setAppsPerRow(mActivityContext.getDeviceProfile().numShownAllAppsColumns);
- mExtraHeight = mActivityContext.getResources().getDimensionPixelSize(
- R.dimen.all_apps_height_extra);
- }
-
- public void setAppsPerRow(int appsPerRow) {
- mAppsPerRow = appsPerRow;
- int totalSpans = mAppsPerRow;
- for (BaseAdapterProvider adapterProvider : mAdapterProviders) {
- for (int itemPerRow : adapterProvider.getSupportedItemsPerRowArray()) {
- if (totalSpans % itemPerRow != 0) {
- totalSpans *= itemPerRow;
- }
- }
- }
- mGridLayoutMgr.setSpanCount(totalSpans);
- }
-
- /**
- * Sets the long click listener for icons
- */
- public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
- mOnIconLongClickListener = listener;
- }
-
- public static boolean isDividerViewType(int viewType) {
- return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
- }
-
- public static boolean isIconViewType(int viewType) {
- return isViewType(viewType, VIEW_TYPE_MASK_ICON);
- }
-
- public static boolean isViewType(int viewType, int viewTypeMask) {
- return (viewType & viewTypeMask) != 0;
- }
-
- public void setIconFocusListener(OnFocusChangeListener focusListener) {
- mIconFocusListener = focusListener;
- }
-
- /**
- * Sets the last search query that was made, used to show when there are no results and to also
- * seed the intent for searching the market.
- */
- public void setLastSearchQuery(String query, OnClickListener marketSearchClickListener) {
- Resources res = mActivityContext.getResources();
- mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
- mMarketSearchClickListener = marketSearchClickListener;
- }
-
- /**
- * Returns the grid layout manager.
- */
- public GridLayoutManager getLayoutManager() {
- return mGridLayoutMgr;
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_ICON:
- int layout = !FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() ? R.layout.all_apps_icon
- : R.layout.all_apps_icon_twoline;
- BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
- layout, parent, false);
- icon.setLongPressTimeoutFactor(1f);
- icon.setOnFocusChangeListener(mIconFocusListener);
- icon.setOnClickListener(mOnIconClickListener);
- icon.setOnLongClickListener(mOnIconLongClickListener);
- // Ensure the all apps icon height matches the workspace icons in portrait mode.
- icon.getLayoutParams().height =
- mActivityContext.getDeviceProfile().allAppsCellHeightPx;
- if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
- icon.getLayoutParams().height += mExtraHeight;
- }
- return new ViewHolder(icon);
- case VIEW_TYPE_EMPTY_SEARCH:
- return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
- parent, false));
- case VIEW_TYPE_SEARCH_MARKET:
- View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
- parent, false);
- searchMarketView.setOnClickListener(mMarketSearchClickListener);
- return new ViewHolder(searchMarketView);
- case VIEW_TYPE_ALL_APPS_DIVIDER:
- return new ViewHolder(mLayoutInflater.inflate(
- R.layout.all_apps_divider, parent, false));
- default:
- BaseAdapterProvider adapterProvider = getAdapterProvider(viewType);
- if (adapterProvider != null) {
- return adapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType);
- }
- throw new RuntimeException("Unexpected view type" + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- switch (holder.getItemViewType()) {
- case VIEW_TYPE_ICON:
- AdapterItem adapterItem = mApps.getAdapterItems().get(position);
- BubbleTextView icon = (BubbleTextView) holder.itemView;
- icon.reset();
- if (adapterItem.itemInfo instanceof AppInfo) {
- icon.applyFromApplicationInfo((AppInfo) adapterItem.itemInfo);
- } else {
- icon.applyFromItemInfoWithIcon(adapterItem.itemInfo);
- }
- break;
- case VIEW_TYPE_EMPTY_SEARCH:
- TextView emptyViewText = (TextView) holder.itemView;
- emptyViewText.setText(mEmptySearchMessage);
- emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
- Gravity.START | Gravity.CENTER_VERTICAL);
- break;
- case VIEW_TYPE_SEARCH_MARKET:
- TextView searchView = (TextView) holder.itemView;
- if (mMarketSearchClickListener != null) {
- searchView.setVisibility(View.VISIBLE);
- } else {
- searchView.setVisibility(View.GONE);
- }
- break;
- case VIEW_TYPE_ALL_APPS_DIVIDER:
- // nothing to do
- break;
- default:
- BaseAdapterProvider adapterProvider = getAdapterProvider(holder.getItemViewType());
- if (adapterProvider != null) {
- adapterProvider.onBindView(holder, position);
- }
- }
- }
-
- @Override
- public void onViewRecycled(@NonNull ViewHolder holder) {
- super.onViewRecycled(holder);
- }
-
- @Override
- public boolean onFailedToRecycleView(ViewHolder holder) {
- // Always recycle and we will reset the view when it is bound
- return true;
- }
-
- @Override
- public int getItemCount() {
- return mApps.getAdapterItems().size();
- }
-
- @Override
- public int getItemViewType(int position) {
- AdapterItem item = mApps.getAdapterItems().get(position);
- return item.viewType;
- }
-
- @Nullable
- private BaseAdapterProvider getAdapterProvider(int viewType) {
- return Arrays.stream(mAdapterProviders).filter(
- adapterProvider -> adapterProvider.isViewSupported(viewType)).findFirst().orElse(
- null);
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index c2cb845..7dbe711 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -19,6 +19,7 @@
import static android.view.View.MeasureSpec.UNSPECIFIED;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
@@ -203,6 +204,7 @@
StatsLogManager mgr = ActivityContext.lookupContext(getContext()).getStatsLogManager();
switch (state) {
case SCROLL_STATE_DRAGGING:
+ mgr.logger().log(LAUNCHER_ALLAPPS_SCROLLED);
requestFocus();
mgr.logger().sendToInteractionJankMonitor(
LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 1a1d521..a4a58b5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -18,7 +18,7 @@
import android.content.Context;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -82,7 +82,7 @@
// The of ordered component names as a result of a search query
private ArrayList<AdapterItem> mSearchResults;
- private AllAppsGridAdapter<T> mAdapter;
+ private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
private final int mNumAppsPerRow;
private int mNumAppRowsInAdapter;
@@ -106,7 +106,7 @@
/**
* Sets the adapter to notify when this dataset changes.
*/
- public void setAdapter(AllAppsGridAdapter<T> adapter) {
+ public void setAdapter(BaseAllAppsAdapter<T> adapter) {
mAdapter = adapter;
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
new file mode 100644
index 0000000..1d1960d
--- /dev/null
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.Arrays;
+
+/**
+ * Adapter for all the apps.
+ *
+ * @param <T> Type of context inflating all apps.
+ */
+public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> extends
+ RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> {
+
+ public static final String TAG = "BaseAllAppsAdapter";
+
+ // A normal icon
+ public static final int VIEW_TYPE_ICON = 1 << 1;
+ // The message shown when there are no filtered results
+ public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2;
+ // The message to continue to a market search when there are no filtered results
+ public static final int VIEW_TYPE_SEARCH_MARKET = 1 << 3;
+
+ // We use various dividers for various purposes. They share enough attributes to reuse layouts,
+ // but differ in enough attributes to require different view types
+
+ // A divider that separates the apps list and the search market button
+ public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
+
+ // 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;
+
+
+ protected final BaseAdapterProvider[] mAdapterProviders;
+
+ /**
+ * ViewHolder for each icon.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ public ViewHolder(View v) {
+ super(v);
+ }
+ }
+
+ /** Sets the number of apps to be displayed in one row of the all apps screen. */
+ public abstract void setAppsPerRow(int appsPerRow);
+
+ /**
+ * Info about a particular adapter item (can be either section or app)
+ */
+ public static class AdapterItem {
+ /** Common properties */
+ // The index of this adapter item in the list
+ public int position;
+ // The type of this item
+ public int viewType;
+
+ // The section name of this item. Note that there can be multiple items with different
+ // sectionNames in the same section
+ public String sectionName = null;
+ // The row that this item shows up on
+ public int rowIndex;
+ // The index of this app in the row
+ public int rowAppIndex;
+ // The associated ItemInfoWithIcon for the item
+ public ItemInfoWithIcon itemInfo = null;
+ // The index of this app not including sections
+ public int appIndex = -1;
+ // Search section associated to result
+ public DecorationInfo decorationInfo = null;
+
+ /**
+ * Factory method for AppIcon AdapterItem
+ */
+ public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = VIEW_TYPE_ICON;
+ item.position = pos;
+ item.sectionName = sectionName;
+ item.itemInfo = appInfo;
+ item.appIndex = appIndex;
+ return item;
+ }
+
+ /**
+ * Factory method for empty search results view
+ */
+ public static AdapterItem asEmptySearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = VIEW_TYPE_EMPTY_SEARCH;
+ item.position = pos;
+ return item;
+ }
+
+ /**
+ * Factory method for a dividerView in AllAppsSearch
+ */
+ public static AdapterItem asAllAppsDivider(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
+ item.position = pos;
+ return item;
+ }
+
+ /**
+ * Factory method for a market search button
+ */
+ public static AdapterItem asMarketSearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = VIEW_TYPE_SEARCH_MARKET;
+ item.position = pos;
+ return item;
+ }
+
+ protected boolean isCountedForAccessibility() {
+ return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
+ }
+ }
+
+ protected final T mActivityContext;
+ protected final AlphabeticalAppsList<T> mApps;
+ // The text to show when there are no search results and no market search handler.
+ protected String mEmptySearchMessage;
+ protected int mAppsPerRow;
+
+ private final LayoutInflater mLayoutInflater;
+ private final OnClickListener mOnIconClickListener;
+ private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+ private OnFocusChangeListener mIconFocusListener;
+ // The click listener to send off to the market app, updated each time the search query changes.
+ private OnClickListener mMarketSearchClickListener;
+ private final int mExtraHeight;
+
+ public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
+ AlphabeticalAppsList<T> apps, BaseAdapterProvider[] adapterProviders) {
+ Resources res = activityContext.getResources();
+ mActivityContext = activityContext;
+ mApps = apps;
+ mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
+ mLayoutInflater = inflater;
+
+ mOnIconClickListener = mActivityContext.getItemOnClickListener();
+
+ mAdapterProviders = adapterProviders;
+ mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
+ }
+
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
+ mOnIconLongClickListener = listener;
+ }
+
+ /** Checks if the passed viewType represents all apps divider. */
+ public static boolean isDividerViewType(int viewType) {
+ return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
+ }
+
+ /** Checks if the passed viewType represents all apps icon. */
+ public static boolean isIconViewType(int viewType) {
+ return isViewType(viewType, VIEW_TYPE_MASK_ICON);
+ }
+
+ public void setIconFocusListener(OnFocusChangeListener focusListener) {
+ mIconFocusListener = focusListener;
+ }
+
+ /**
+ * Sets the last search query that was made, used to show when there are no results and to also
+ * seed the intent for searching the market.
+ */
+ public void setLastSearchQuery(String query, OnClickListener marketSearchClickListener) {
+ Resources res = mActivityContext.getResources();
+ mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
+ mMarketSearchClickListener = marketSearchClickListener;
+ }
+
+ /**
+ * Returns the layout manager.
+ */
+ public abstract RecyclerView.LayoutManager getLayoutManager();
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ICON:
+ int layout = !FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() ? R.layout.all_apps_icon
+ : R.layout.all_apps_icon_twoline;
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
+ layout, parent, false);
+ icon.setLongPressTimeoutFactor(1f);
+ icon.setOnFocusChangeListener(mIconFocusListener);
+ icon.setOnClickListener(mOnIconClickListener);
+ icon.setOnLongClickListener(mOnIconLongClickListener);
+ // Ensure the all apps icon height matches the workspace icons in portrait mode.
+ icon.getLayoutParams().height =
+ mActivityContext.getDeviceProfile().allAppsCellHeightPx;
+ if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
+ icon.getLayoutParams().height += mExtraHeight;
+ }
+ return new ViewHolder(icon);
+ case VIEW_TYPE_EMPTY_SEARCH:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
+ parent, false));
+ case VIEW_TYPE_SEARCH_MARKET:
+ View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
+ parent, false);
+ searchMarketView.setOnClickListener(mMarketSearchClickListener);
+ return new ViewHolder(searchMarketView);
+ case VIEW_TYPE_ALL_APPS_DIVIDER:
+ return new ViewHolder(mLayoutInflater.inflate(
+ R.layout.all_apps_divider, parent, false));
+ default:
+ BaseAdapterProvider adapterProvider = getAdapterProvider(viewType);
+ if (adapterProvider != null) {
+ return adapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType);
+ }
+ throw new RuntimeException("Unexpected view type" + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ switch (holder.getItemViewType()) {
+ case VIEW_TYPE_ICON:
+ AdapterItem adapterItem = mApps.getAdapterItems().get(position);
+ BubbleTextView icon = (BubbleTextView) holder.itemView;
+ icon.reset();
+ if (adapterItem.itemInfo instanceof AppInfo) {
+ icon.applyFromApplicationInfo((AppInfo) adapterItem.itemInfo);
+ } else {
+ icon.applyFromItemInfoWithIcon(adapterItem.itemInfo);
+ }
+ break;
+ case VIEW_TYPE_EMPTY_SEARCH:
+ TextView emptyViewText = (TextView) holder.itemView;
+ emptyViewText.setText(mEmptySearchMessage);
+ emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+ Gravity.START | Gravity.CENTER_VERTICAL);
+ break;
+ case VIEW_TYPE_SEARCH_MARKET:
+ TextView searchView = (TextView) holder.itemView;
+ if (mMarketSearchClickListener != null) {
+ searchView.setVisibility(View.VISIBLE);
+ } else {
+ searchView.setVisibility(View.GONE);
+ }
+ break;
+ case VIEW_TYPE_ALL_APPS_DIVIDER:
+ // nothing to do
+ break;
+ default:
+ BaseAdapterProvider adapterProvider = getAdapterProvider(holder.getItemViewType());
+ if (adapterProvider != null) {
+ adapterProvider.onBindView(holder, position);
+ }
+ }
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull ViewHolder holder) {
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(ViewHolder holder) {
+ // Always recycle and we will reset the view when it is bound
+ return true;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mApps.getAdapterItems().size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ AdapterItem item = mApps.getAdapterItems().get(position);
+ return item.viewType;
+ }
+
+ protected static boolean isViewType(int viewType, int viewTypeMask) {
+ return (viewType & viewTypeMask) != 0;
+ }
+
+ @Nullable
+ protected BaseAdapterProvider getAdapterProvider(int viewType) {
+ return Arrays.stream(mAdapterProviders).filter(
+ adapterProvider -> adapterProvider.isViewSupported(viewType)).findFirst().orElse(
+ null);
+ }
+}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index bfc7515..f542d8e 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -44,7 +44,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.ColorUtils;
-import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
@@ -697,18 +696,21 @@
return ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio);
}
+ protected abstract BaseAllAppsAdapter getAdapter(AlphabeticalAppsList<T> mAppsList,
+ BaseAdapterProvider[] adapterProviders);
+
protected int getHeaderBottom() {
return (int) getTranslationY();
}
- /** Holds a {@link AllAppsGridAdapter} and related fields. */
+ /** Holds a {@link BaseAllAppsAdapter} and related fields. */
public class AdapterHolder {
public static final int MAIN = 0;
public static final int WORK = 1;
private final boolean mIsWork;
- public final AllAppsGridAdapter<T> adapter;
- final LinearLayoutManager mLayoutManager;
+ public final BaseAllAppsAdapter<T> adapter;
+ final RecyclerView.LayoutManager mLayoutManager;
final AlphabeticalAppsList<T> mAppsList;
final Rect mPadding = new Rect();
AllAppsRecyclerView mRecyclerView;
@@ -724,8 +726,7 @@
mWorkManager.getAdapterProvider()}
: new BaseAdapterProvider[]{mMainAdapterProvider};
- adapter = new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), mAppsList,
- adapterProviders);
+ adapter = getAdapter(mAppsList, adapterProviders);
mAppsList.setAdapter(adapter);
mLayoutManager = adapter.getLayoutManager();
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 0137e2a..fd8945a 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -33,7 +33,7 @@
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 4a886a4..cb459ea 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -38,9 +38,9 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.search.SearchCallback;
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 1f854c6..222c8fe 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -23,7 +23,7 @@
import androidx.annotation.AnyThread;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 2459e09..cc17064 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -87,6 +87,7 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
@@ -128,7 +129,8 @@
public PreviewContext(Context base, InvariantDeviceProfile idp) {
super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
- CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE);
+ CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE,
+ WindowManagerProxy.INSTANCE);
mIdp = idp;
mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
mObjectMap.put(LauncherAppState.INSTANCE,
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 0c1ba8b..f392e95 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -527,7 +527,11 @@
LAUNCHER_TASKBAR_LONGPRESS_SHOW(897),
@UiEvent(doc = "User clicks on the search icon on header to launch search in app.")
- LAUNCHER_ALLAPPS_SEARCHINAPP_LAUNCH(913);
+ LAUNCHER_ALLAPPS_SEARCHINAPP_LAUNCH(913),
+
+ @UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search "
+ + "result page etc.")
+ LAUNCHER_ALLAPPS_SCROLLED(985);
// ADD MORE
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index b45c97b..c554d06 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -17,15 +17,11 @@
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
-import android.graphics.Insets;
-import android.os.Build;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.WindowInsets;
import androidx.annotation.CallSuper;
-import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherRootView;
@@ -179,14 +175,6 @@
}
/**
- * Gives subclasses a chance to override some window insets (via
- * {@link android.view.WindowInsets.Builder#setInsets(int, Insets)}).
- */
- @RequiresApi(api = Build.VERSION_CODES.R)
- public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder,
- WindowInsets oldInsets) { }
-
- /**
* Runs the given {@param r} runnable when this activity binds to the touch interaction service.
*/
public void runOnBindToTouchInteractionService(Runnable r) {
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 867fd99..8b425da 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,7 +21,7 @@
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 2bf3b14..05fe182 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -125,7 +125,6 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String TASK_VIEW_ID_CRASH = "b/195430732";
public static final String NO_DROP_TARGET = "b/195031154";
public static final String NULL_INT_SET = "b/200572078";
public static final String MISSING_PROMISE_ICON = "b/202985412";
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index f944d3c..22e3de8 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -26,10 +26,8 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
-import static com.android.launcher3.util.WindowManagerCompat.MIN_LARGE_TABLET_WIDTH;
-import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
-
-import static java.util.Collections.emptyMap;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_LARGE_TABLET_WIDTH;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -38,12 +36,13 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Build;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import androidx.annotation.AnyThread;
@@ -52,11 +51,12 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Map;
+import java.util.Collections;
import java.util.Objects;
import java.util.Set;
@@ -89,6 +89,7 @@
// Null for SDK < S
private final Context mWindowContext;
+
// The callback in this listener updates DeviceProfile, which other listeners might depend on
private DisplayInfoChangeListener mPriorityListener;
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
@@ -115,23 +116,9 @@
mContext.registerReceiver(mReceiver,
getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
+ WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
mInfo = new Info(getDisplayInfoContext(display), display,
- getInternalDisplays(mDM), emptyMap());
- }
-
- private static ArrayMap<String, PortraitSize> getInternalDisplays(
- DisplayManager displayManager) {
- Display[] displays = displayManager.getDisplays();
- ArrayMap<String, PortraitSize> internalDisplays = new ArrayMap<>();
- for (Display display : displays) {
- if (ApiWrapper.isInternalDisplay(display)) {
- Point size = new Point();
- display.getRealSize(size);
- internalDisplays.put(ApiWrapper.getUniqueId(display),
- new PortraitSize(size.x, size.y));
- }
- }
- return internalDisplays;
+ wmProxy, wmProxy.estimateInternalDisplayBounds(context));
}
/**
@@ -226,16 +213,17 @@
@AnyThread
private void handleInfoChange(Display display) {
+ WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
Info oldInfo = mInfo;
Context displayContext = getDisplayInfoContext(display);
- Info newInfo = new Info(displayContext, display,
- oldInfo.mInternalDisplays, oldInfo.mPerDisplayBounds);
+ Info newInfo = new Info(displayContext, display, wmProxy, oldInfo.mPerDisplayBounds);
if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
|| newInfo.navigationMode != oldInfo.navigationMode) {
// Cache may not be valid anymore, recreate without cache
- newInfo = new Info(displayContext, display, getInternalDisplays(mDM), emptyMap());
+ newInfo = new Info(displayContext, display, wmProxy,
+ wmProxy.estimateInternalDisplayBounds(displayContext));
}
int change = 0;
@@ -254,9 +242,8 @@
if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)) {
change |= CHANGE_SUPPORTED_BOUNDS;
- PortraitSize realSize = new PortraitSize(newInfo.currentSize.x, newInfo.currentSize.y);
- PortraitSize expectedSize = oldInfo.mInternalDisplays.get(
- ApiWrapper.getUniqueId(display));
+ Point currentS = newInfo.currentSize;
+ Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size;
if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
Log.e("b/198965093",
"Inconsistent number of displays"
@@ -264,7 +251,9 @@
+ "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
+ "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
}
- if (!realSize.equals(expectedSize) && display.getState() == Display.STATE_OFF) {
+ if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
+ || Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
+ && display.getState() == Display.STATE_OFF) {
Log.e("b/198965093", "Display size changed while display is off, ignoring change");
return;
}
@@ -290,30 +279,38 @@
public static class Info {
- // Configuration properties
+ // Cached property
public final int rotation;
+ public final String displayId;
+ public final Point currentSize;
+ public final Rect cutout;
+
+ // Configuration property
public final float fontScale;
public final int densityDpi;
public final NavigationMode navigationMode;
private final PortraitSize mScreenSizeDp;
- public final Point currentSize;
-
- public String displayId;
public final Set<WindowBounds> supportedBounds = new ArraySet<>();
- private final Map<String, Set<WindowBounds>> mPerDisplayBounds = new ArrayMap<>();
- private final ArrayMap<String, PortraitSize> mInternalDisplays;
+
+ private final ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> mPerDisplayBounds =
+ new ArrayMap<>();
public Info(Context context, Display display) {
- this(context, display, new ArrayMap<>(), emptyMap());
+ /* don't need system overrides for external displays */
+ this(context, display, new WindowManagerProxy(), new ArrayMap<>());
}
- private Info(Context context, Display display,
- ArrayMap<String, PortraitSize> internalDisplays,
- Map<String, Set<WindowBounds>> perDisplayBoundsCache) {
- mInternalDisplays = internalDisplays;
- rotation = display.getRotation();
+ // Used for testing
+ public Info(Context context, Display display,
+ WindowManagerProxy wmProxy,
+ ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache) {
+ CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(display);
+ rotation = displayInfo.rotation;
+ currentSize = displayInfo.size;
+ displayId = displayInfo.id;
+ cutout = displayInfo.cutout;
Configuration config = context.getResources().getConfiguration();
fontScale = config.fontScale;
@@ -321,54 +318,29 @@
mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp);
navigationMode = parseNavigationMode(context);
- currentSize = new Point();
- display.getRealSize(currentSize);
+ mPerDisplayBounds.putAll(perDisplayBoundsCache);
+ Pair<CachedDisplayInfo, WindowBounds[]> cachedValue = mPerDisplayBounds.get(displayId);
- displayId = ApiWrapper.getUniqueId(display);
- Set<WindowBounds> currentSupportedBounds =
- getSupportedBoundsForDisplay(display, currentSize);
- mPerDisplayBounds.put(displayId, currentSupportedBounds);
- supportedBounds.addAll(currentSupportedBounds);
-
- if (ApiWrapper.isInternalDisplay(display) && internalDisplays.size() > 1) {
- int displayCount = internalDisplays.size();
- for (int i = 0; i < displayCount; i++) {
- String displayKey = internalDisplays.keyAt(i);
- if (TextUtils.equals(displayId, displayKey)) {
- continue;
- }
-
- Set<WindowBounds> displayBounds = perDisplayBoundsCache.get(displayKey);
- if (displayBounds == null) {
- // We assume densityDpi is the same across all internal displays
- displayBounds = WindowManagerCompat.estimateDisplayProfiles(
- context, internalDisplays.valueAt(i), densityDpi,
- ApiWrapper.TASKBAR_DRAWN_IN_PROCESS);
- }
-
- supportedBounds.addAll(displayBounds);
- mPerDisplayBounds.put(displayKey, displayBounds);
+ WindowBounds realBounds = wmProxy.getRealBounds(context, display, displayInfo);
+ if (cachedValue == null) {
+ supportedBounds.add(realBounds);
+ } else {
+ // Verify that the real bounds are a match
+ WindowBounds expectedBounds = cachedValue.second[displayInfo.rotation];
+ if (!realBounds.equals(expectedBounds)) {
+ WindowBounds[] clone = new WindowBounds[4];
+ System.arraycopy(cachedValue.second, 0, clone, 0, 4);
+ clone[displayInfo.rotation] = realBounds;
+ cachedValue = Pair.create(displayInfo.normalize(), clone);
+ mPerDisplayBounds.put(displayId, cachedValue);
}
}
+ mPerDisplayBounds.values().forEach(
+ pair -> Collections.addAll(supportedBounds, pair.second));
Log.d("b/211775278", "displayId: " + displayId + ", currentSize: " + currentSize);
Log.d("b/211775278", "perDisplayBounds: " + mPerDisplayBounds);
}
- private static Set<WindowBounds> getSupportedBoundsForDisplay(Display display, Point size) {
- Point smallestSize = new Point();
- Point largestSize = new Point();
- display.getCurrentSizeRange(smallestSize, largestSize);
-
- int portraitWidth = Math.min(size.x, size.y);
- int portraitHeight = Math.max(size.x, size.y);
- Set<WindowBounds> result = new ArraySet<>();
- result.add(new WindowBounds(portraitWidth, portraitHeight,
- smallestSize.x, largestSize.y));
- result.add(new WindowBounds(portraitHeight, portraitWidth,
- largestSize.x, smallestSize.y));
- return result;
- }
-
/**
* Returns {@code true} if the bounds represent a tablet.
*/
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 6329540..8485371 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -15,10 +15,17 @@
*/
package com.android.launcher3.util;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -34,6 +41,9 @@
Math.max(Runtime.getRuntime().availableProcessors(), 2);
private static final int KEEP_ALIVE = 1;
+ /** Dedicated executor instances for work depending on other packages. */
+ private static final Map<String, ExecutorService> PACKAGE_EXECUTORS = new ConcurrentHashMap<>();
+
/**
* An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size.
*/
@@ -76,6 +86,18 @@
new LooperExecutor(createAndStartNewLooper("launcher-loader"));
/**
+ * Returns and caches a single thread executor for a given package.
+ *
+ * @param packageName Package associated with the executor.
+ */
+ public static ExecutorService getPackageExecutor(String packageName) {
+ return PACKAGE_EXECUTORS.computeIfAbsent(
+ packageName,
+ p -> newSingleThreadExecutor(
+ new SimpleThreadFactory(p, THREAD_PRIORITY_BACKGROUND)));
+ }
+
+ /**
* A simple ThreadFactory to set the thread name and priority when used with executors.
*/
public static class SimpleThreadFactory implements ThreadFactory {
diff --git a/src/com/android/launcher3/util/RotationUtils.java b/src/com/android/launcher3/util/RotationUtils.java
new file mode 100644
index 0000000..3414a3d
--- /dev/null
+++ b/src/com/android/launcher3/util/RotationUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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 static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Utility methods based on {@code frameworks/base/core/java/android/util/RotationUtils.java}
+ */
+public class RotationUtils {
+
+ /**
+ * Rotates an Rect according to the given rotation.
+ */
+ public static void rotateRect(Rect rect, int rotation) {
+ switch (rotation) {
+ case ROTATION_0:
+ return;
+ case ROTATION_90:
+ rect.set(rect.top, rect.right, rect.bottom, rect.left);
+ return;
+ case ROTATION_180:
+ rect.set(rect.right, rect.bottom, rect.left, rect.top);
+ return;
+ case ROTATION_270:
+ rect.set(rect.bottom, rect.left, rect.top, rect.right);
+ return;
+ default:
+ throw new IllegalArgumentException("unknown rotation: " + rotation);
+ }
+ }
+
+ /**
+ * Rotates an size according to the given rotation.
+ */
+ public static void rotateSize(Point size, int rotation) {
+ switch (rotation) {
+ case ROTATION_0:
+ case ROTATION_180:
+ return;
+ case ROTATION_90:
+ case ROTATION_270:
+ size.set(size.y, size.x);
+ return;
+ default:
+ throw new IllegalArgumentException("unknown rotation: " + rotation);
+ }
+ }
+
+ /** @return the rotation needed to rotate from oldRotation to newRotation. */
+ public static int deltaRotation(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+}
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
index c92770e..a15679a 100644
--- a/src/com/android/launcher3/util/WindowBounds.java
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -33,19 +33,27 @@
public final Rect bounds;
public final Rect insets;
public final Point availableSize;
+ public final int rotationHint;
public WindowBounds(Rect bounds, Rect insets) {
+ this(bounds, insets, -1);
+ }
+
+ public WindowBounds(Rect bounds, Rect insets, int rotationHint) {
this.bounds = bounds;
this.insets = insets;
+ this.rotationHint = rotationHint;
availableSize = new Point(bounds.width() - insets.left - insets.right,
bounds.height() - insets.top - insets.bottom);
}
- public WindowBounds(int width, int height, int availableWidth, int availableHeight) {
+ public WindowBounds(int width, int height, int availableWidth, int availableHeight,
+ int rotationHint) {
this.bounds = new Rect(0, 0, width, height);
this.availableSize = new Point(availableWidth, availableHeight);
// We don't care about insets in this case
this.insets = new Rect(0, 0, width - availableWidth, height - availableHeight);
+ this.rotationHint = rotationHint;
}
@Override
diff --git a/src/com/android/launcher3/util/WindowManagerCompat.java b/src/com/android/launcher3/util/WindowManagerCompat.java
deleted file mode 100644
index 873a518..0000000
--- a/src/com/android/launcher3/util/WindowManagerCompat.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
-import static com.android.launcher3.Utilities.dpiFromPx;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.ArraySet;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DisplayController.PortraitSize;
-
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * Utility class to estimate window manager values
- */
-@TargetApi(Build.VERSION_CODES.S)
-public class WindowManagerCompat {
-
- public static final int MIN_TABLET_WIDTH = 600;
- public static final int MIN_LARGE_TABLET_WIDTH = 720;
-
- /**
- * Returns a set of supported render sizes for a internal display.
- * This is a temporary workaround which assumes only nav-bar insets change across displays, and
- * is only used until we eventually get the real values
- * @param consumeTaskBar if true, it assumes that task bar is part of the app window
- * and ignores any insets because of task bar.
- */
- public static Set<WindowBounds> estimateDisplayProfiles(
- Context windowContext, PortraitSize size, int densityDpi, boolean consumeTaskBar) {
- if (!Utilities.ATLEAST_S) {
- return Collections.emptySet();
- }
- WindowInsets defaultInsets = windowContext.getSystemService(WindowManager.class)
- .getMaximumWindowMetrics().getWindowInsets();
- boolean isGesturalMode = ResourceUtils.getIntegerByName(
- "config_navBarInteractionMode",
- windowContext.getResources(),
- INVALID_RESOURCE_HANDLE) == 2;
-
- WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(defaultInsets);
- Set<WindowBounds> result = new ArraySet<>();
- int swDP = (int) dpiFromPx(size.width, densityDpi);
- boolean isTablet = swDP >= MIN_TABLET_WIDTH;
-
- final Insets portraitNav, landscapeNav;
- if (isTablet && !consumeTaskBar) {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources()
- .getDimensionPixelSize(R.dimen.taskbar_size));
- } else if (!isGesturalMode) {
- portraitNav = Insets.of(0, 0, 0,
- getSystemResource(windowContext, "navigation_bar_height", swDP));
- landscapeNav = isTablet
- ? Insets.of(0, 0, 0, getSystemResource(windowContext,
- "navigation_bar_height_landscape", swDP))
- : Insets.of(0, 0, getSystemResource(windowContext,
- "navigation_bar_width", swDP), 0);
- } else {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, 0);
- }
-
- result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
- new Rect(0, 0, size.width, size.height),
- insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build())));
- result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
- new Rect(0, 0, size.height, size.width),
- insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build())));
- return result;
- }
-
- private static int getSystemResource(Context context, String key, int swDp) {
- int resourceId = context.getResources().getIdentifier(key, "dimen", "android");
- if (resourceId > 0) {
- Configuration conf = new Configuration();
- conf.smallestScreenWidthDp = swDp;
- return context.createConfigurationContext(conf)
- .getResources().getDimensionPixelSize(resourceId);
- }
- return 0;
- }
-}
diff --git a/src/com/android/launcher3/util/window/CachedDisplayInfo.java b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
new file mode 100644
index 0000000..06b9829
--- /dev/null
+++ b/src/com/android/launcher3/util/window/CachedDisplayInfo.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import static com.android.launcher3.util.RotationUtils.deltaRotation;
+import static com.android.launcher3.util.RotationUtils.rotateRect;
+import static com.android.launcher3.util.RotationUtils.rotateSize;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import java.util.Objects;
+
+/**
+ * Properties on a display
+ */
+public class CachedDisplayInfo {
+
+ public final String id;
+ public final Point size;
+ public final int rotation;
+ public final Rect cutout;
+
+ public CachedDisplayInfo() {
+ this(new Point(0, 0), 0);
+ }
+
+ public CachedDisplayInfo(Point size, int rotation) {
+ this("", size, rotation, new Rect());
+ }
+
+ public CachedDisplayInfo(String id, Point size, int rotation, Rect cutout) {
+ this.id = id;
+ this.size = size;
+ this.rotation = rotation;
+ this.cutout = cutout;
+ }
+
+ /**
+ * Returns a CachedDisplayInfo where the properties are normalized to {@link Surface#ROTATION_0}
+ */
+ public CachedDisplayInfo normalize() {
+ if (rotation == Surface.ROTATION_0) {
+ return this;
+ }
+ Point newSize = new Point(size);
+ rotateSize(newSize, deltaRotation(rotation, Surface.ROTATION_0));
+
+ Rect newCutout = new Rect(cutout);
+ rotateRect(newCutout, deltaRotation(rotation, Surface.ROTATION_0));
+ return new CachedDisplayInfo(id, newSize, Surface.ROTATION_0, newCutout);
+ }
+
+ @Override
+ public String toString() {
+ return "CachedDisplayInfo{"
+ + "id='" + id + '\''
+ + ", size=" + size
+ + ", rotation=" + rotation
+ + ", cutout=" + cutout
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CachedDisplayInfo)) return false;
+ CachedDisplayInfo that = (CachedDisplayInfo) o;
+ return rotation == that.rotation && Objects.equals(id, that.id)
+ && Objects.equals(size, that.size) && Objects.equals(cutout,
+ that.cutout);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, size, rotation, cutout);
+ }
+}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
new file mode 100644
index 0000000..ba3d981
--- /dev/null
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.ResourceUtils.getDimenByName;
+import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.launcher3.util.RotationUtils.deltaRotation;
+import static com.android.launcher3.util.RotationUtils.rotateRect;
+import static com.android.launcher3.util.RotationUtils.rotateSize;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Surface;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.WindowBounds;
+
+/**
+ * Utility class for mocking some window manager behaviours
+ */
+public class WindowManagerProxy implements ResourceBasedOverride {
+
+ public static final int MIN_TABLET_WIDTH = 600;
+ public static final int MIN_LARGE_TABLET_WIDTH = 720;
+
+ public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
+ forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class);
+
+ protected final boolean mTaskbarDrawnInProcess;
+
+ /**
+ * Creates a new instance of proxy, applying any overrides
+ */
+ public static WindowManagerProxy newInstance(Context context) {
+ return Overrides.getObject(WindowManagerProxy.class, context,
+ R.string.window_manager_proxy_class);
+ }
+
+ public WindowManagerProxy() {
+ this(false);
+ }
+
+ protected WindowManagerProxy(boolean taskbarDrawnInProcess) {
+ mTaskbarDrawnInProcess = taskbarDrawnInProcess;
+ }
+
+ /**
+ * Returns a map of normalized info of internal displays to estimated window bounds
+ * for that display
+ */
+ public ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> estimateInternalDisplayBounds(
+ Context context) {
+ Display[] displays = context.getSystemService(DisplayManager.class).getDisplays();
+ ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> result = new ArrayMap<>();
+ for (Display display : displays) {
+ if (isInternalDisplay(display)) {
+ CachedDisplayInfo info = getDisplayInfo(display).normalize();
+ WindowBounds[] bounds = estimateWindowBounds(context, info);
+ result.put(info.id, Pair.create(info, bounds));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the real bounds for the provided display after applying any insets normalization
+ */
+ @TargetApi(Build.VERSION_CODES.R)
+ public WindowBounds getRealBounds(Context windowContext,
+ Display display, CachedDisplayInfo info) {
+ if (!Utilities.ATLEAST_R) {
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+
+ if (info.size.y > info.size.x) {
+ // Portrait
+ return new WindowBounds(info.size.x, info.size.y, smallestSize.x, largestSize.y,
+ info.rotation);
+ } else {
+ // Landscape
+ new WindowBounds(info.size.x, info.size.y, largestSize.x, smallestSize.y,
+ info.rotation);
+ }
+ }
+
+ WindowMetrics wm = windowContext.getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics();
+
+ Rect insets = new Rect();
+ normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets);
+ return new WindowBounds(wm.getBounds(), insets, info.rotation);
+ }
+
+ /**
+ * Returns an updated insets, accounting for various Launcher UI specific overrides like taskbar
+ */
+ @TargetApi(Build.VERSION_CODES.R)
+ public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
+ Rect outInsets) {
+ if (!Utilities.ATLEAST_R || !mTaskbarDrawnInProcess) {
+ outInsets.set(oldInsets.getSystemWindowInsetLeft(), oldInsets.getSystemWindowInsetTop(),
+ oldInsets.getSystemWindowInsetRight(), oldInsets.getSystemWindowInsetBottom());
+ return oldInsets;
+ }
+
+ WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(oldInsets);
+ Insets navInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
+
+ Resources systemRes = context.getResources();
+ Configuration config = systemRes.getConfiguration();
+
+ boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
+ boolean isGesture = isGestureNav(context);
+
+ int bottomNav = isTablet
+ ? 0
+ : (config.screenHeightDp > config.screenWidthDp
+ ? getDimenByName(NAVBAR_HEIGHT, systemRes, 0)
+ : (isGesture
+ ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0)
+ : 0));
+ Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
+ insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
+ insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
+
+ // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
+ // would count towards it). This is used for the bottom protection in All Apps for example.
+ if (isGesture) {
+ Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
+ Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
+ oldTappableInsets.right, 0);
+ insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
+ }
+
+ WindowInsets result = insetsBuilder.build();
+ Insets systemWindowInsets = result.getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ outInsets.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right,
+ systemWindowInsets.bottom);
+ return result;
+ }
+
+ /**
+ * Returns true if the display is an internal displays
+ */
+ protected boolean isInternalDisplay(Display display) {
+ return display.getDisplayId() == Display.DEFAULT_DISPLAY;
+ }
+
+ /**
+ * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
+ */
+ public WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo display) {
+ int densityDpi = context.getResources().getConfiguration().densityDpi;
+ int rotation = display.rotation;
+ Rect safeCutout = display.cutout;
+
+ int minSize = Math.min(display.size.x, display.size.y);
+ int swDp = (int) dpiFromPx(minSize, densityDpi);
+
+ Resources systemRes;
+ {
+ Configuration conf = new Configuration();
+ conf.smallestScreenWidthDp = swDp;
+ systemRes = context.createConfigurationContext(conf).getResources();
+ }
+
+ boolean isTablet = swDp >= MIN_TABLET_WIDTH;
+ boolean isTabletOrGesture = isTablet
+ || (Utilities.ATLEAST_R && isGestureNav(context));
+
+ int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+
+ int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
+
+ navBarHeightPortrait = isTablet
+ ? (mTaskbarDrawnInProcess
+ ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
+ : getDimenByName(NAVBAR_HEIGHT, systemRes, 0);
+
+ navBarHeightLandscape = isTablet
+ ? (mTaskbarDrawnInProcess
+ ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
+ : (isTabletOrGesture
+ ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0);
+ navbarWidthLandscape = isTabletOrGesture
+ ? 0
+ : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0);
+
+ WindowBounds[] result = new WindowBounds[4];
+ Point tempSize = new Point();
+ for (int i = 0; i < 4; i++) {
+ int rotationChange = deltaRotation(rotation, i);
+ tempSize.set(display.size.x, display.size.y);
+ rotateSize(tempSize, rotationChange);
+ Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);
+
+ int navBarHeight, navbarWidth;
+ if (tempSize.y > tempSize.x) {
+ navBarHeight = navBarHeightPortrait;
+ navbarWidth = 0;
+ } else {
+ navBarHeight = navBarHeightLandscape;
+ navbarWidth = navbarWidthLandscape;
+ }
+
+ Rect insets = new Rect(safeCutout);
+ rotateRect(insets, rotationChange);
+ insets.top = Math.max(insets.top, statusBarHeight);
+ insets.bottom = Math.max(insets.bottom, navBarHeight);
+
+ if (i == Surface.ROTATION_270 || i == Surface.ROTATION_180) {
+ // On reverse landscape (and in rare-case when the natural orientation of the
+ // device is landscape), navigation bar is on the right.
+ insets.left = Math.max(insets.left, navbarWidth);
+ } else {
+ insets.right = Math.max(insets.right, navbarWidth);
+ }
+ result[i] = new WindowBounds(bounds, insets, i);
+ }
+ return result;
+ }
+
+ protected boolean isGestureNav(Context context) {
+ return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
+ context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
+ }
+
+ /**
+ * Returns a CachedDisplayInfo initialized for the current display
+ */
+ @TargetApi(Build.VERSION_CODES.S)
+ public CachedDisplayInfo getDisplayInfo(Display display) {
+ int rotation = display.getRotation();
+
+ Point size = new Point();
+ display.getRealSize(size);
+
+ Rect cutoutRect = new Rect();
+ if (Utilities.ATLEAST_S) {
+ DisplayCutout cutout = display.getCutout();
+ if (cutout != null) {
+ cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
+ cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
+ }
+ }
+
+ return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect);
+ }
+
+ /**
+ * Returns a unique ID representing the display
+ */
+ protected String getDisplayId(Display display) {
+ return Integer.toString(display.getDisplayId());
+ }
+
+ /**
+ * Returns rotation of the display associated with the context.
+ */
+ public int getRotation(Context context) {
+ Display d = null;
+ if (Utilities.ATLEAST_R) {
+ try {
+ d = context.getDisplay();
+ } catch (UnsupportedOperationException e) {
+ // Ignore
+ }
+ }
+ return d == null ? DisplayController.INSTANCE.get(context).getInfo().rotation
+ : d.getRotation();
+ }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 81e3f98..6715749 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -19,7 +19,6 @@
import android.app.Person;
import android.content.Context;
import android.content.pm.ShortcutInfo;
-import android.view.Display;
import com.android.launcher3.Utilities;
@@ -32,20 +31,6 @@
}
/**
- * Returns true if the display is an internal displays
- */
- public static boolean isInternalDisplay(Display display) {
- return display.getDisplayId() == Display.DEFAULT_DISPLAY;
- }
-
- /**
- * Returns a unique ID representing the display
- */
- public static String getUniqueId(Display display) {
- return Integer.toString(display.getDisplayId());
- }
-
- /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
diff --git a/tests/src/com/android/launcher3/DeviceProfileTest.kt b/tests/src/com/android/launcher3/DeviceProfileTest.kt
index 60046a0..75ad21d 100644
--- a/tests/src/com/android/launcher3/DeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileTest.kt
@@ -139,7 +139,7 @@
else
Pair(1440, 3120)
- windowBounds = WindowBounds(x, y, x, y - 100)
+ windowBounds = WindowBounds(x, y, x, y - 100, 0)
`when`(info.isTablet(any())).thenReturn(false)
`when`(info.isLargeTablet(any())).thenReturn(false)
@@ -153,7 +153,7 @@
else
Pair(1600, 2560)
- windowBounds = WindowBounds(x, y, x, y - 100)
+ windowBounds = WindowBounds(x, y, x, y - 100, 0)
`when`(info.isTablet(any())).thenReturn(true)
`when`(info.isLargeTablet(any())).thenReturn(false)
@@ -167,7 +167,7 @@
else
Pair(1600, 2560)
- windowBounds = WindowBounds(x, y, x, y - 100)
+ windowBounds = WindowBounds(x, y, x, y - 100, 0)
`when`(info.isTablet(any())).thenReturn(true)
`when`(info.isLargeTablet(any())).thenReturn(true)
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 59966ee..3324959 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -67,6 +67,7 @@
import com.android.launcher3.testing.TestInformationProvider;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import org.mockito.ArgumentCaptor;
@@ -501,7 +502,7 @@
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
- ItemInstallQueue.INSTANCE);
+ ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
}