Merge "Migrate KeyguardIndicationController to use LogBuffer" into tm-qpr-dev
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
index 80d1457..7b154a5 100644
--- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
@@ -55,7 +54,6 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Trace.beginSection("RemeasuringLinearLayout#onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
int height = 0;
@@ -88,6 +86,5 @@
}
mMatchParentViews.clear();
setMeasuredDimension(getMeasuredWidth(), height);
- Trace.endSection();
}
}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_componentsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 839edc8..3de1045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -601,7 +601,7 @@
animator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0e..5533ad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
- in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -108,9 +108,8 @@
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b83..cdc8cdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mMultiInstancesComponents;
- private StageCoordinator mStageCoordinator;
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
+
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -471,72 +521,116 @@
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
/** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
+ private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+ @SplitPosition int position, int taskId) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
- return false;
- }
+ final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+ if (launchingActivity == null) return false;
- if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
- return false;
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
-
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ return false;
}
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ if (!isSplitScreenVisible()) {
+ // Split screen is not yet activated, check if the current top running task is valid to
+ // split together.
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
+ return false;
+ }
+
+ // Compare to the adjacent side of the split to determine if this is launching the same
+ // component adjacently.
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String componentName = launching.flattenToString();
+ for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+ if (mMultiInstancesComponents[i].equals(componentName)) {
+ return true;
+ }
}
return false;
@@ -839,14 +933,13 @@
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
- int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, options1, taskId, options2,
- splitPosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
@@ -872,14 +965,13 @@
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
- fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
- remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e888c6f..c2ab7ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,7 +24,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this,
- aborted -> {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- } /* consumedCallback */,
+ null /* consumedCallback */,
(finishWct, finishT) -> {
if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@
/** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -473,12 +471,6 @@
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show();
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
- }
}
// Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@
va.start();
});
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1617,10 +1612,7 @@
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d3..38b75f8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -91,18 +99,21 @@
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
@@ -148,58 +159,100 @@
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
- // Verify launching the same activity returns true.
+ public void testStartIntent_appendsNoUserActionFlag() {
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
- doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
}
companion object {
- private const val TAG = "ThemedBatteryDrawable"
- private const val WIDTH = 12f
- private const val HEIGHT = 20f
+ const val WIDTH = 12f
+ const val HEIGHT = 20f
private const val CRITICAL_LEVEL = 15
// On a 12x20 grid, how wide to make the fill protection stroke.
// Scales when our size changes
private const val PROTECTION_STROKE_WIDTH = 3f
// Arbitrarily chosen for visibility at small sizes
- private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+ const val PROTECTION_MIN_STROKE_WIDTH = 6f
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce9829b..55d6379 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -485,6 +485,12 @@
<!-- Whether to show a severe low battery dialog. -->
<bool name="config_severe_battery_dialog">false</bool>
+ <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+ needed. This path is a 10px wide and 13px tall. -->
+ <string name="config_batterymeterShieldPath" translatable="false">
+ M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+ </string>
+
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 691bf89..c78d36d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+ <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+ @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+ the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+ bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+ <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index eead934..4146e20 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -316,7 +316,7 @@
<!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_unlock_button">Unlock</string>
+ <string name="accessibility_unlock_button">Unlocked</string>
<!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_lock_icon">Device locked</string>
<!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -439,11 +439,17 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
+ <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+
+ <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index af4be1a..e07a6c1 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
@@ -57,7 +57,7 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
@@ -65,6 +65,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
@@ -80,12 +81,16 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
<Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index d8a4e77..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -43,6 +43,7 @@
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<Transform
android:scaleX="2.57"
@@ -53,7 +54,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
@@ -67,16 +68,15 @@
<Constraint
android:id="@+id/carrier_group">
<Layout
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- android:minHeight="@dimen/large_screen_shade_header_min_height"
app:layout_constraintWidth_min="48dp"
- android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<PropertySet
android:alpha="1"
@@ -86,7 +86,7 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
@@ -108,6 +108,7 @@
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.systemui.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+ private val context: Context,
+ frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+ private val mainBatteryDrawable: ThemedBatteryDrawable
+ get() = drawable as ThemedBatteryDrawable
+
+ private val shieldPath = Path()
+ private val scaledShield = Path()
+ private val scaleMatrix = Matrix()
+
+ private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+ private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+ private var density = context.resources.displayMetrics.density
+
+ private val dualTone =
+ context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+ private val shieldTransparentOutlinePaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.TRANSPARENT
+ p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ p.style = Paint.Style.FILL_AND_STROKE
+ }
+
+ private val shieldPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.MAGENTA
+ p.style = Paint.Style.FILL
+ p.isDither = true
+ }
+
+ init {
+ loadPaths()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ updateSizes()
+ }
+
+ var displayShield: Boolean = false
+
+ private fun updateSizes() {
+ val b = bounds
+ if (b.isEmpty) {
+ return
+ }
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+ val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+ drawable?.setBounds(
+ b.left,
+ b.top,
+ /* right= */ b.left + mainWidth.toInt(),
+ /* bottom= */ b.top + mainHeight.toInt()
+ )
+
+ if (displayShield) {
+ val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+ val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+ scaleMatrix.setScale(sx, sy)
+ shieldPath.transform(scaleMatrix, scaledShield)
+
+ shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+ shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+ val scaledStrokeWidth =
+ (sx * SHIELD_STROKE).coerceAtLeast(
+ ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ )
+ shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+ }
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ val height =
+ if (displayShield) {
+ BATTERY_HEIGHT_WITH_SHIELD
+ } else {
+ BATTERY_HEIGHT
+ }
+ return (height * density).toInt()
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ val width =
+ if (displayShield) {
+ BATTERY_WIDTH_WITH_SHIELD
+ } else {
+ BATTERY_WIDTH
+ }
+ return (width * density).toInt()
+ }
+
+ override fun draw(c: Canvas) {
+ c.saveLayer(null, null)
+ // Draw the main battery icon
+ super.draw(c)
+
+ if (displayShield) {
+ c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+ // We need a transparent outline around the shield, so first draw the transparent-ness
+ // then draw the shield
+ c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+ c.drawPath(scaledShield, shieldPaint)
+ }
+ c.restore()
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.OPAQUE
+ }
+
+ override fun setAlpha(p0: Int) {
+ // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+ }
+
+ override fun setColorFilter(colorfilter: ColorFilter?) {
+ super.setColorFilter(colorFilter)
+ shieldPaint.colorFilter = colorFilter
+ }
+
+ /** Sets whether the battery is currently charging. */
+ fun setCharging(charging: Boolean) {
+ mainBatteryDrawable.charging = charging
+ }
+
+ /** Sets the current level (out of 100) of the battery. */
+ fun setBatteryLevel(level: Int) {
+ mainBatteryDrawable.setBatteryLevel(level)
+ }
+
+ /** Sets whether power save is enabled. */
+ fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+ mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+ }
+
+ /** Returns whether power save is currently enabled. */
+ fun getPowerSaveEnabled(): Boolean {
+ return mainBatteryDrawable.powerSaveEnabled
+ }
+
+ /** Sets the colors to use for the icon. */
+ fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+ shieldPaint.color = if (dualTone) fgColor else singleToneColor
+ mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+ }
+
+ /** Notifies this drawable that the density might have changed. */
+ fun notifyDensityChanged() {
+ density = context.resources.displayMetrics.density
+ }
+
+ private fun loadPaths() {
+ val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+ shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
- private final ThemedBatteryDrawable mDrawable;
+ private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
private TextView mBatteryPercentView;
@@ -77,7 +76,10 @@
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
+ private String mEstimateText = null;
private boolean mCharging;
+ private boolean mIsOverheated;
+ private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
- mDrawable = new ThemedBatteryDrawable(context, frameColor);
+ mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
if (mode == mShowPercentMode) return;
mShowPercentMode = mode;
updateShowPercent();
+ updatePercentText();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updatePercentView();
+ mDrawable.notifyDensityChanged();
}
public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
mDrawable.setPowerSaveEnabled(isPowerSave);
}
+ void onIsOverheatedChanged(boolean isOverheated) {
+ boolean valueChanged = mIsOverheated != isOverheated;
+ mIsOverheated = isOverheated;
+ if (valueChanged) {
+ updateContentDescription();
+ // The battery drawable is a different size depending on whether it's currently
+ // overheated or not, so we need to re-scale the view when overheated changes.
+ scaleBatteryMeterViews();
+ }
+ }
+
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
mBatteryEstimateFetcher = fetcher;
}
+ void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+ mDisplayShieldEnabled = displayShieldEnabled;
+ }
+
void updatePercentText() {
if (mBatteryStateUnknown) {
- setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return;
}
if (mBatteryEstimateFetcher == null) {
+ setPercentTextAtCurrentLevel();
return;
}
@@ -245,10 +264,9 @@
return;
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+ mEstimateText = estimate;
mBatteryPercentView.setText(estimate);
- setContentDescription(getContext().getString(
- R.string.accessibility_battery_level_with_estimate,
- mLevel, estimate));
+ updateContentDescription();
} else {
setPercentTextAtCurrentLevel();
}
@@ -257,28 +275,49 @@
setPercentTextAtCurrentLevel();
}
} else {
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ updateContentDescription();
}
}
private void setPercentTextAtCurrentLevel() {
- if (mBatteryPercentView == null) {
- return;
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+ // Setting text actually triggers a layout pass (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). Avoid needless
+ // relayout if the text didn't actually change.
+ if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+ mBatteryPercentView.setText(percentText);
+ }
}
- String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
- mBatteryPercentView.setText(percentText);
+ updateContentDescription();
+ }
+
+ private void updateContentDescription() {
+ Context context = getContext();
+
+ String contentDescription;
+ if (mBatteryStateUnknown) {
+ contentDescription = context.getString(R.string.accessibility_battery_unknown);
+ } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+ contentDescription = context.getString(
+ mIsOverheated
+ ? R.string.accessibility_battery_level_charging_paused_with_estimate
+ : R.string.accessibility_battery_level_with_estimate,
+ mLevel,
+ mEstimateText);
+ } else if (mIsOverheated) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+ } else if (mCharging) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging, mLevel);
+ } else {
+ contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
}
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ setContentDescription(contentDescription);
}
void updateShowPercent() {
@@ -329,6 +368,7 @@
}
mBatteryStateUnknown = isUnknown;
+ updateContentDescription();
if (mBatteryStateUnknown) {
mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
float iconScaleFactor = typedValue.getFloat();
- int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
- int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+ float mainBatteryHeight =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+ float mainBatteryWidth =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+ // If the battery is marked as overheated, we should display a shield indicating that the
+ // battery is being "defended".
+ boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+ float fullBatteryIconHeight =
+ BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+ float fullBatteryIconWidth =
+ BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+ int marginTop;
+ if (displayShield) {
+ // If the shield is displayed, we need some extra marginTop so that the bottom of the
+ // main icon is still aligned with the bottom of all the other system icons.
+ int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+ // However, the other system icons have some embedded bottom padding that the battery
+ // doesn't have, so we shouldn't move the battery icon down by the full amount.
+ // See b/258672854.
+ marginTop = shieldHeightAddition
+ - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+ } else {
+ marginTop = 0;
+ }
+
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
- (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
- scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+ Math.round(fullBatteryIconWidth),
+ Math.round(fullBatteryIconHeight));
+ scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
+ mDrawable.setDisplayShield(displayShield);
mBatteryIconView.setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.invalidateDrawable(mDrawable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..77cb9d1 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -29,6 +29,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -84,6 +86,11 @@
public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(isUnknown);
}
+
+ @Override
+ public void onIsOverheatedChanged(boolean isOverheated) {
+ mView.onIsOverheatedChanged(isOverheated);
+ }
};
// Some places may need to show the battery conditionally, and not obey the tuner
@@ -98,6 +105,7 @@
BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
mConfigurationController = configurationController;
@@ -106,6 +114,7 @@
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+ mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
mSettingObserver = new SettingObserver(mainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.systemui.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+ /** Width of the main battery icon, not including the shield. */
+ const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+ /** Height of the main battery icon, not including the shield. */
+ const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+ private const val SHIELD_WIDTH = 10f
+ private const val SHIELD_HEIGHT = 13f
+
+ /**
+ * Amount that the left side of the shield should be offset from the left side of the battery.
+ */
+ const val SHIELD_LEFT_OFFSET = 8f
+ /** Amount that the top of the shield should be offset from the top of the battery. */
+ const val SHIELD_TOP_OFFSET = 10f
+
+ const val SHIELD_STROKE = 4f
+
+ /** The full width of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+ /** The full height of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+ /**
+ * Given the desired height of the main battery icon in pixels, returns the height that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+ * extends slightly below the bottom of the main battery icon so we need some extra height.
+ */
+ @JvmStatic
+ fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryHeight
+ } else {
+ val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+ verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the desired width of the main battery icon in pixels, returns the width that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+ * past the right side of the main battery icon so we need some extra width.
+ */
+ @JvmStatic
+ fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryWidth
+ } else {
+ val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+ horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the height of the full battery icon, return how tall the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+ * up some of the view's height so the main battery width will be just a portion of
+ * [fullBatteryHeight].
+ */
+ @JvmStatic
+ fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryHeight
+ } else {
+ return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+ }
+ }
+
+ /**
+ * Given the width of the full battery icon, return how wide the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+ * up some of the view's width so the main battery width will be just a portion of
+ * [fullBatteryWidth].
+ */
+ @JvmStatic
+ fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryWidth
+ } else {
+ return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index fe89c9a..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,24 +21,21 @@
import com.android.systemui.dagger.qualifiers.InstrumentationTest;
import com.android.systemui.util.InitializationChecker;
-import javax.inject.Singleton;
-
import dagger.BindsInstance;
-import dagger.Component;
/**
* Base root component for Dagger injection.
*
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
* See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
*/
-@Singleton
-@Component(modules = {GlobalModule.class})
public interface GlobalRootComponent {
/**
* Builder for a GlobalRootComponent.
*/
- @Component.Builder
interface Builder {
@BindsInstance
Builder context(Context context);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 002ffdb..19fd25d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -82,7 +82,10 @@
// TODO(b/257506350): Tracking Bug
val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
- // next id: 118
+ // TODO(b/257315550): Tracking Bug
+ val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+ // next id: 119
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -239,6 +242,9 @@
// TODO(b/256613548): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+ // TODO(b/256623670): Tracking Bug
+ @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
@@ -380,7 +386,8 @@
// TODO(b/254513155): Tracking Bug
@JvmField
- val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy")
+ val SCREENSHOT_WORK_PROFILE_POLICY =
+ unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..954534d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -92,7 +92,6 @@
centerEnd,
ConstraintSet.END
)
- constrainWidth(R.id.statusIcons, 0)
},
qsConstraintsChanges = {
setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index dc90266..615f230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -226,6 +226,7 @@
BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController
) {
return new BatteryMeterViewController(
@@ -235,6 +236,7 @@
broadcastDispatcher,
mainHandler,
contentResolver,
+ featureFlags,
batteryController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
default void onWirelessChargingChanged(boolean isWirlessCharging) {
}
+
+ default void onIsOverheatedChanged(boolean isOverheated) {
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..3c2ac7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
protected boolean mPowerSave;
private boolean mAodPowerSave;
private boolean mWirelessCharging;
+ private boolean mIsOverheated = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -184,6 +188,7 @@
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
+ cb.onIsOverheatedChanged(mIsOverheated);
}
@Override
@@ -222,6 +227,13 @@
fireBatteryUnknownStateChanged();
}
+ int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+ if (isOverheated != mIsOverheated) {
+ mIsOverheated = isOverheated;
+ fireIsOverheatedChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -292,6 +304,10 @@
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
+ public boolean isOverheated() {
+ return mIsOverheated;
+ }
+
@Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +418,15 @@
}
}
+ private void fireIsOverheatedChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+ }
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +437,7 @@
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
String present = args.getString("present");
+ String overheated = args.getString("overheated");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -426,6 +452,10 @@
mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged();
}
+ if (overheated != null) {
+ mIsOverheated = overheated.equals("true");
+ fireIsOverheatedChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.systemui.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+ @Test
+ fun intrinsicSize_shieldFalse_isBatterySize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = false
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+ }
+
+ @Test
+ fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = true
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight)
+ .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+ }
+
+ // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..bc8f961 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -35,6 +35,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -59,6 +61,7 @@
private Handler mHandler;
@Mock
private ContentResolver mContentResolver;
+ private FakeFeatureFlags mFeatureFlags;
@Mock
private BatteryController mBatteryController;
@@ -71,19 +74,13 @@
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
- mController = new BatteryMeterViewController(
- mBatteryMeterView,
- mConfigurationController,
- mTunerService,
- mBroadcastDispatcher,
- mHandler,
- mContentResolver,
- mBatteryController
- );
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
}
@Test
public void onViewAttached_callbacksRegistered() {
+ initController();
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
@Test
public void onViewDetached_callbacksUnregistered() {
+ initController();
// Set everything up first.
mController.onViewAttached();
@@ -114,6 +112,7 @@
@Test
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+ initController();
// Start out receiving tuner updates
mController.onViewAttached();
@@ -124,10 +123,43 @@
@Test
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+ initController();
+
mController.ignoreTunerUpdates();
mController.onViewAttached();
verify(mTunerService, never()).addTunable(any(), any());
}
+
+ @Test
+ public void shieldFlagDisabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+ }
+
+ @Test
+ public void shieldFlagEnabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+ }
+
+ private void initController() {
+ mController = new BatteryMeterViewController(
+ mBatteryMeterView,
+ mConfigurationController,
+ mTunerService,
+ mBroadcastDispatcher,
+ mHandler,
+ mContentResolver,
+ mFeatureFlags,
+ mBatteryController
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
// No assert needed
}
+ @Test
+ fun contentDescription_unknown() {
+ mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_unknown)
+ )
+ }
+
+ @Test
+ fun contentDescription_estimate() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_estimateAndOverheated() {
+ mBatteryMeterView.onBatteryLevelChanged(17, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 17,
+ ESTIMATE,
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_overheated() {
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+ }
+
+ @Test
+ fun contentDescription_charging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 45)
+ )
+ }
+
+ @Test
+ fun contentDescription_notCharging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 45)
+ )
+ }
+
+ @Test
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+
+ // Update the show mode from estimate to percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+ assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 15)
+ )
+ }
+
+ @Test
+ fun contentDescription_manyUpdates_alwaysUpdated() {
+ // Overheated
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+
+ // Overheated & estimate
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+ mBatteryMeterView.updatePercentText()
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just estimate
+ mBatteryMeterView.onIsOverheatedChanged(false)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 90)
+ )
+
+ // Charging
+ mBatteryMeterView.onBatteryLevelChanged(90, true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 90)
+ )
+ }
+
+ @Test
+ fun isOverheatedChanged_true_drawableGetsTrue() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isTrue()
+ }
+
+ @Test
+ fun isOverheatedChanged_false_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ // Start as true
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ // Update to false
+ mBatteryMeterView.onIsOverheatedChanged(false)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ @Test
+ fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(false)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+ return (mBatteryMeterView.getChildAt(0) as ImageView)
+ .drawable as AccessorizedBatteryDrawable
+ }
+
private class Fetcher : BatteryEstimateFetcher {
override fun fetchBatteryTimeRemainingEstimate(
completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
private companion object {
const val ESTIMATE = "2 hours 2 minutes"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.systemui.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+ @Test
+ fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+ val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+ assertThat(fullHeight).isEqualTo(56f)
+ }
+
+ @Test
+ fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+ val mainHeight = BATTERY_HEIGHT * 5
+ val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+ // Since the main battery was scaled 5x, the output height should also be scaled 5x
+ val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+ assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+ val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+ assertThat(fullWidth).isEqualTo(33f)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+ val mainWidth = BATTERY_WIDTH * 3.3f
+
+ val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+ // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+ val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+ assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+ val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+ assertThat(mainHeight).isEqualTo(89f)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+ val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+ val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+ // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+ val expectedHeight = BATTERY_HEIGHT * 7.7f
+ assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+ val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+ assertThat(mainWidth).isEqualTo(2345f)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+ val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+ // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+ val expectedWidth = BATTERY_WIDTH * 0.6f
+ assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 0ce9056..d7eb337 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -320,6 +321,48 @@
assertThat(changes.largeScreenConstraintsChanges).isNull()
}
+ @Test
+ fun testRelevantViewsAreNotMatchConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name has 0 height in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ assertWithMessage("$name has 0 height in qs")
+ .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qs")
+ .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
+ assertWithMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
+ }
+ }
+
private operator fun ConstraintsChanges.invoke() {
qqsConstraintsChanges?.invoke(qqsConstraint)
qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
Assert.assertFalse(mBatteryController.isChargingSourceDock());
}
+
+ @Test
+ public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_healthOverheated_outputsTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_noHealthGiven_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e51976b..ef1baf6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -123,6 +123,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -146,6 +147,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+
/**
* Manages attached displays.
* <p>
@@ -1880,6 +1882,14 @@
if (displayDevice == null) {
return;
}
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
+ c.getCurve().first,
+ c.getCurve().second,
+ // should not be logged for virtual displays
+ uniqueId);
+ }
mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
userSerial, packageName);
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index fe691c6..54664f5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WaitResult.INVALID_DELAY;
@@ -2592,6 +2593,10 @@
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
+ if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+ targetActivity.mPendingRemoteAnimation =
+ activityOptions.getRemoteAnimationAdapter();
+ }
targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();