Merge "Controls UI - Loading animations" into rvc-dev
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
index c43fabd..68c27a8 100644
--- a/apex/permission/framework/Android.bp
+++ b/apex/permission/framework/Android.bp
@@ -21,18 +21,12 @@
path: "java",
}
-java_sdk_library {
+java_library {
name: "framework-permission",
- defaults: ["framework-module-defaults"],
srcs: [
":framework-permission-sources",
],
-
- // TODO(b/155480189) - Remove naming_scheme once references have been resolved.
- // Temporary java_sdk_library component naming scheme to use to ease the transition from separate
- // modules to java_sdk_library.
- naming_scheme: "framework-modules",
-
+ sdk_version: "module_current",
apex_available: [
"com.android.permission",
"test_com.android.permission",
@@ -46,5 +40,91 @@
visibility: [
"//frameworks/base/apex/permission:__subpackages__",
],
- stubs_library_visibility: ["//visibility:public"],
+}
+
+stubs_defaults {
+ name: "framework-permission-stubs-defaults",
+ srcs: [ ":framework-permission-sources" ],
+ libs: [ "framework-annotations-lib" ],
+ dist: { dest: "framework-permission.txt" },
+}
+
+droidstubs {
+ name: "framework-permission-stubs-srcs-publicapi",
+ defaults: [
+ "framework-module-stubs-defaults-publicapi",
+ "framework-permission-stubs-defaults",
+ ],
+ check_api: {
+ last_released: {
+ api_file: ":framework-permission.api.public.latest",
+ removed_api_file: ":framework-permission-removed.api.public.latest",
+ },
+ api_lint: {
+ new_since: ":framework-permission.api.public.latest",
+ },
+ },
+}
+
+droidstubs {
+ name: "framework-permission-stubs-srcs-systemapi",
+ defaults: [
+ "framework-module-stubs-defaults-systemapi",
+ "framework-permission-stubs-defaults",
+ ],
+ check_api: {
+ last_released: {
+ api_file: ":framework-permission.api.system.latest",
+ removed_api_file: ":framework-permission-removed.api.system.latest",
+ },
+ api_lint: {
+ new_since: ":framework-permission.api.system.latest",
+ },
+ },
+}
+
+droidstubs {
+ name: "framework-permission-api-module_libs_api",
+ defaults: [
+ "framework-module-api-defaults-module_libs_api",
+ "framework-permission-stubs-defaults",
+ ],
+ check_api: {
+ last_released: {
+ api_file: ":framework-permission.api.module-lib.latest",
+ removed_api_file: ":framework-permission-removed.api.module-lib.latest",
+ },
+ api_lint: {
+ new_since: ":framework-permission.api.module-lib.latest",
+ },
+ },
+}
+
+droidstubs {
+ name: "framework-permission-stubs-srcs-module_libs_api",
+ defaults: [
+ "framework-module-stubs-defaults-module_libs_api",
+ "framework-permission-stubs-defaults",
+ ],
+}
+
+java_library {
+ name: "framework-permission-stubs-publicapi",
+ srcs: [ ":framework-permission-stubs-srcs-publicapi" ],
+ defaults: ["framework-module-stubs-lib-defaults-publicapi"],
+ dist: { dest: "framework-permission.jar" },
+}
+
+java_library {
+ name: "framework-permission-stubs-systemapi",
+ srcs: [ ":framework-permission-stubs-srcs-systemapi" ],
+ defaults: ["framework-module-stubs-lib-defaults-systemapi"],
+ dist: { dest: "framework-permission.jar" },
+}
+
+java_library {
+ name: "framework-permission-stubs-module_libs_api",
+ srcs: [ ":framework-permission-stubs-srcs-module_libs_api" ],
+ defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
+ dist: { dest: "framework-permission.jar" },
}
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java
index aa23251..42724be 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/BrightnessSynchronizer.java
@@ -84,17 +84,17 @@
* Converts between the int brightness system and the float brightness system.
*/
public static float brightnessIntToFloat(Context context, int brightnessInt) {
- PowerManager pm = context.getSystemService(PowerManager.class);
- float pmMinBrightness = pm.getBrightnessConstraint(
+ final PowerManager pm = context.getSystemService(PowerManager.class);
+ final float pmMinBrightness = pm.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float pmMaxBrightness = pm.getBrightnessConstraint(
+ final float pmMaxBrightness = pm.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
- int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
+ final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
+ final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt,
pmMinBrightness, pmMaxBrightness);
@@ -119,34 +119,43 @@
* Converts between the float brightness system and the int brightness system.
*/
public static int brightnessFloatToInt(Context context, float brightnessFloat) {
- PowerManager pm = context.getSystemService(PowerManager.class);
- float pmMinBrightness = pm.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float pmMaxBrightness = pm.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
- int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
-
- return brightnessFloatToInt(brightnessFloat, pmMinBrightness, pmMaxBrightness,
- minBrightnessInt, maxBrightnessInt);
+ return Math.round(brightnessFloatToIntRange(context, brightnessFloat));
}
/**
- * Converts between the float brightness system and the int brightness system.
+ * Converts between the float brightness system and the int brightness system, but returns
+ * the converted value as a float within the int-system's range. This method helps with
+ * conversions from one system to the other without losing the floating-point precision.
*/
- public static int brightnessFloatToInt(float brightnessFloat, float minFloat, float maxFloat,
- int minInt, int maxInt) {
+ public static float brightnessFloatToIntRange(Context context, float brightnessFloat) {
+ final PowerManager pm = context.getSystemService(PowerManager.class);
+ final float minFloat = pm.getBrightnessConstraint(
+ PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ final float maxFloat = pm.getBrightnessConstraint(
+ PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ final float minInt = brightnessFloatToIntRange(minFloat,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+ final float maxInt = brightnessFloatToIntRange(maxFloat,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+ return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt);
+ }
+
+ /**
+ * Translates specified value from the float brightness system to the int brightness system,
+ * given the min/max of each range. Accounts for special values such as OFF and invalid values.
+ * Value returned as a float privimite (to preserve precision), but is a value within the
+ * int-system range.
+ */
+ private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat,
+ float maxFloat, float minInt, float maxInt) {
if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
return PowerManager.BRIGHTNESS_OFF;
} else if (Float.isNaN(brightnessFloat)) {
return PowerManager.BRIGHTNESS_INVALID;
} else {
- return Math.round(MathUtils.constrainedMap((float) minInt, (float) maxInt, minFloat,
- maxFloat, brightnessFloat));
+ return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index cff669e..9950e55 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3506,13 +3506,11 @@
}
/**
- * Only expand direct share area if there is a minimum number of shortcuts,
- * which will help reduce the amount of visible shuffling due to older-style
- * direct share targets.
+ * Only expand direct share area if there is a minimum number of targets.
*/
private boolean canExpandDirectShare() {
int orientation = getResources().getConfiguration().orientation;
- return mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
+ return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow()
&& orientation == Configuration.ORIENTATION_PORTRAIT
&& !isInMultiWindowMode();
}
@@ -3721,8 +3719,12 @@
// only expand if we have more than maxTargetsPerRow, and delay that decision
// until they start to scroll
- if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
+ ChooserListAdapter adapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ int validTargets =
+ mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
+ : adapter.getSelectableServiceTargetCount();
+ if (validTargets <= maxTargetsPerRow) {
mHideDirectShareExpansion = true;
return;
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 492f98c..2568d09 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -79,6 +79,7 @@
private static final int MAX_SUGGESTED_APP_TARGETS = 4;
private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+ private static final int MAX_SERVICE_TARGET_APP = 8;
static final int MAX_SERVICE_TARGETS = 8;
@@ -98,6 +99,7 @@
private ChooserTargetInfo
mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
private int mValidServiceTargetsNum = 0;
+ private int mAvailableServiceTargetsNum = 0;
private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
mParkingDirectShareTargets = new HashMap<>();
private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
@@ -603,7 +605,13 @@
Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
mParkingDirectShareTargets.getOrDefault(origComponentName,
new Pair<>(new ArrayList<>(), 0));
- parkingTargetInfoPair.first.addAll(parkingTargetInfos);
+ for (ChooserTargetInfo target : parkingTargetInfos) {
+ if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
+ && !checkDuplicateTarget(target, mServiceTargets)) {
+ parkingTargetInfoPair.first.add(target);
+ mAvailableServiceTargetsNum++;
+ }
+ }
mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
rankTargetsWithinComponent(origComponentName);
if (isShortcutResult) {
@@ -648,7 +656,7 @@
List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
int insertedNum = parkingTargetsItem.second;
while (insertedNum < quota && !parkingTargets.isEmpty()) {
- if (!checkDuplicateTarget(parkingTargets.get(0))) {
+ if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
mValidServiceTargetsNum++;
insertedNum++;
@@ -663,9 +671,6 @@
+ " totalScore=" + totalScore
+ " quota=" + quota);
}
- if (mShortcutComponents.contains(component)) {
- mNumShortcutResults += insertedNum - parkingTargetsItem.second;
- }
mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
}
if (!shouldWaitPendingService) {
@@ -681,19 +686,15 @@
return;
}
Log.i(TAG, " fillAllServiceTargets");
- int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
- List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
+ List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
// Append all remaining targets of top recommended components into direct share row.
for (ComponentName component : topComponentNames) {
if (!mParkingDirectShareTargets.containsKey(component)) {
continue;
}
mParkingDirectShareTargets.get(component).first.stream()
- .filter(target -> !checkDuplicateTarget(target))
+ .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
.forEach(target -> {
- if (mShortcutComponents.contains(component)) {
- mNumShortcutResults++;
- }
mServiceTargets.add(mValidServiceTargetsNum, target);
mValidServiceTargetsNum++;
});
@@ -706,28 +707,34 @@
.map(pair -> pair.first)
.forEach(targets -> {
for (ChooserTargetInfo target : targets) {
- if (!checkDuplicateTarget(target)) {
+ if (!checkDuplicateTarget(target, mServiceTargets)) {
mServiceTargets.add(mValidServiceTargetsNum, target);
mValidServiceTargetsNum++;
- mNumShortcutResults++;
}
}
});
mParkingDirectShareTargets.clear();
}
- private boolean checkDuplicateTarget(ChooserTargetInfo chooserTargetInfo) {
+ private boolean checkDuplicateTarget(ChooserTargetInfo target,
+ List<ChooserTargetInfo> destination) {
// Check for duplicates and abort if found
- for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
- if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+ for (ChooserTargetInfo otherTargetInfo : destination) {
+ if (target.isSimilar(otherTargetInfo)) {
return true;
}
}
return false;
}
- int getNumShortcutResults() {
- return mNumShortcutResults;
+ /**
+ * The return number have to exceed a minimum limit to make direct share area expandable. When
+ * append direct share targets is enabled, return count of all available targets parking in the
+ * memory; otherwise, it is shortcuts count which will help reduce the amount of visible
+ * shuffling due to older-style direct share targets.
+ */
+ int getNumServiceTargetsForExpand() {
+ return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
}
/**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e670f1f..4510b87 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -685,6 +685,7 @@
</activity>
<activity android:name=".controls.management.ControlsEditingActivity"
+ android:label="@string/controls_menu_edit"
android:theme="@style/Theme.ControlsManagement"
android:excludeFromRecents="true"
android:noHistory="true"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 76ca385..09918e7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -169,5 +169,8 @@
<item type="id" name="screen_recording_options" />
<item type="id" name="screen_recording_dialog_source_text" />
<item type="id" name="screen_recording_dialog_source_description" />
+
+ <item type="id" name="accessibility_action_controls_move_before" />
+ <item type="id" name="accessibility_action_controls_move_after" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8c10f61..8bbcfa0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2711,6 +2711,8 @@
<string name="accessibility_control_change_favorite">favorite</string>
<!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
<string name="accessibility_control_change_unfavorite">unfavorite</string>
+ <!-- a11y action to move a control to the position specified by the parameter [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_move">Move to position <xliff:g id="number" example="1">%d</xliff:g></string>
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
<string name="controls_favorite_default_title">Controls</string>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 175ed06..00a406e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -48,6 +48,8 @@
private var modified = false
+ override val moveHelper = null
+
override val favorites: List<ControlInfo>
get() = favoriteIds.mapNotNull { id ->
val control = controls.firstOrNull { it.control.controlId == id }?.control
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 4b283d6..2f91710 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.graphics.Rect
+import android.os.Bundle
import android.service.controls.Control
import android.service.controls.DeviceTypes
import android.view.LayoutInflater
@@ -78,7 +79,7 @@
background = parent.context.getDrawable(
R.drawable.control_background_ripple)
},
- model is FavoritesModel // Indicates that position information is needed
+ model?.moveHelper // Indicates that position information is needed
) { id, favorite ->
model?.changeFavoriteStatus(id, favorite)
}
@@ -176,12 +177,14 @@
/**
* Holder for using with [ControlStatusWrapper] to display names of zones.
+ * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no
+ * rearranging
* @param favoriteCallback this callback will be called whenever the favorite state of the
* [Control] this view represents changes.
*/
internal class ControlHolder(
view: View,
- val withPosition: Boolean,
+ val moveHelper: ControlsModel.MoveHelper?,
val favoriteCallback: ModelFavoriteChanger
) : Holder(view) {
private val favoriteStateDescription =
@@ -197,7 +200,11 @@
visibility = View.VISIBLE
}
- private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
+ private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
+ this::stateDescription,
+ this::getLayoutPosition,
+ moveHelper
+ )
init {
ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
@@ -207,7 +214,7 @@
private fun stateDescription(favorite: Boolean): CharSequence? {
if (!favorite) {
return notFavoriteStateDescription
- } else if (!withPosition) {
+ } else if (moveHelper == null) {
return favoriteStateDescription
} else {
val position = layoutPosition + 1
@@ -256,15 +263,67 @@
}
}
+/**
+ * Accessibility delegate for [ControlHolder].
+ *
+ * Provides the following functionality:
+ * * Sets the state description indicating whether the controls is Favorited or Unfavorited
+ * * Adds the position to the state description if necessary.
+ * * Adds context action for moving (rearranging) a control.
+ *
+ * @param stateRetriever function to determine the state description based on the favorite state
+ * @param positionRetriever function to obtain the position of this control. It only has to be
+ * correct in controls that are currently favorites (and therefore can
+ * be moved).
+ * @param moveHelper helper interface to determine if a control can be moved and actually move it.
+ */
private class ControlHolderAccessibilityDelegate(
- val stateRetriever: (Boolean) -> CharSequence?
+ val stateRetriever: (Boolean) -> CharSequence?,
+ val positionRetriever: () -> Int,
+ val moveHelper: ControlsModel.MoveHelper?
) : AccessibilityDelegateCompat() {
var isFavorite = false
+ companion object {
+ private val MOVE_BEFORE_ID = R.id.accessibility_action_controls_move_before
+ private val MOVE_AFTER_ID = R.id.accessibility_action_controls_move_after
+ }
+
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
+ info.isContextClickable = false
+ addClickAction(host, info)
+ maybeAddMoveBeforeAction(host, info)
+ maybeAddMoveAfterAction(host, info)
+
+ // Determine the stateDescription based on the holder information
+ info.stateDescription = stateRetriever(isFavorite)
+ // Remove the information at the end indicating row and column.
+ info.setCollectionItemInfo(null)
+
+ info.className = Switch::class.java.name
+ }
+
+ override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true
+ }
+ return when (action) {
+ MOVE_BEFORE_ID -> {
+ moveHelper?.moveBefore(positionRetriever())
+ true
+ }
+ MOVE_AFTER_ID -> {
+ moveHelper?.moveAfter(positionRetriever())
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) {
// Change the text for the double-tap action
val clickActionString = if (isFavorite) {
host.context.getString(R.string.accessibility_control_change_unfavorite)
@@ -276,13 +335,30 @@
// “favorite/unfavorite”
clickActionString)
info.addAction(click)
+ }
- // Determine the stateDescription based on the holder information
- info.stateDescription = stateRetriever(isFavorite)
- // Remove the information at the end indicating row and column.
- info.setCollectionItemInfo(null)
+ private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) {
+ if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) {
+ val newPosition = positionRetriever() + 1 - 1
+ val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ MOVE_BEFORE_ID,
+ host.context.getString(R.string.accessibility_control_move, newPosition)
+ )
+ info.addAction(moveBefore)
+ info.isContextClickable = true
+ }
+ }
- info.className = Switch::class.java.name
+ private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) {
+ if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) {
+ val newPosition = positionRetriever() + 1 + 1
+ val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ MOVE_AFTER_ID,
+ host.context.getString(R.string.accessibility_control_move, newPosition)
+ )
+ info.addAction(moveAfter)
+ info.isContextClickable = true
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 37b6d15..2543953 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -42,6 +42,8 @@
*/
val elements: List<ElementWrapper>
+ val moveHelper: MoveHelper?
+
/**
* Change the favorite status of a particular control.
*/
@@ -69,6 +71,34 @@
*/
fun onFirstChange()
}
+
+ /**
+ * Interface to facilitate moving controls from an [AccessibilityDelegate].
+ *
+ * All positions should be 0 based.
+ */
+ interface MoveHelper {
+
+ /**
+ * Whether the control in `position` can be moved to the position before it.
+ */
+ fun canMoveBefore(position: Int): Boolean
+
+ /**
+ * Whether the control in `position` can be moved to the position after it.
+ */
+ fun canMoveAfter(position: Int): Boolean
+
+ /**
+ * Move the control in `position` to the position before it.
+ */
+ fun moveBefore(position: Int)
+
+ /**
+ * Move the control in `position` to the position after it.
+ */
+ fun moveAfter(position: Int)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 411170cb..5242501 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.management
import android.content.ComponentName
+import android.util.Log
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.controls.ControlInterface
@@ -39,9 +40,39 @@
private val favoritesModelCallback: FavoritesModelCallback
) : ControlsModel {
+ companion object {
+ private const val TAG = "FavoritesModel"
+ }
+
private var adapter: RecyclerView.Adapter<*>? = null
private var modified = false
+ override val moveHelper = object : ControlsModel.MoveHelper {
+ override fun canMoveBefore(position: Int): Boolean {
+ return position > 0 && position < dividerPosition
+ }
+
+ override fun canMoveAfter(position: Int): Boolean {
+ return position >= 0 && position < dividerPosition - 1
+ }
+
+ override fun moveBefore(position: Int) {
+ if (!canMoveBefore(position)) {
+ Log.w(TAG, "Cannot move position $position before")
+ } else {
+ onMoveItem(position, position - 1)
+ }
+ }
+
+ override fun moveAfter(position: Int) {
+ if (!canMoveAfter(position)) {
+ Log.w(TAG, "Cannot move position $position after")
+ } else {
+ onMoveItem(position, position + 1)
+ }
+ }
+ }
+
override fun attachAdapter(adapter: RecyclerView.Adapter<*>) {
this.adapter = adapter
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cf098d5..960c501 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -156,7 +156,7 @@
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification
- notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+ notificationManager.cancel(NOTIFICATION_VIEW_ID);
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -175,7 +175,7 @@
Toast.LENGTH_LONG).show();
// Remove notification
- notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+ notificationManager.cancel(NOTIFICATION_VIEW_ID);
Log.d(TAG, "Deleted recording " + uri);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9cfb1b2..839ea69 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -56,6 +56,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
@@ -160,6 +161,9 @@
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+ // From WizardManagerHelper.java
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
private static final String TAG = "GlobalScreenshot";
private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
@@ -460,6 +464,13 @@
return;
}
+ if (!isUserSetupComplete()) {
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(finisher);
+ return;
+ }
+
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
@@ -546,6 +557,41 @@
}
/**
+ * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+ * failure).
+ */
+ private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mScreenshotHandler.post(() -> {
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ });
+
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(SavedImageData imageData) {
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+
+ mScreenshotHandler.post(() -> {
+ Toast.makeText(mContext, R.string.screenshot_saved_title,
+ Toast.LENGTH_SHORT).show();
+ });
+ }
+ }
+ });
+ }
+
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
+ /**
* Clears current screenshot
*/
private void dismissScreenshot(String reason, boolean immediate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
new file mode 100644
index 0000000..7f7ff9cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * Logger class for events related to the user clicking on notification actions
+ */
+class ActionClickLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logInitialClick(
+ entry: NotificationEntry?,
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ str2 = entry?.ranking?.channel?.id
+ str3 = pendingIntent.intent.toString()
+ }, {
+ "ACTION CLICK $str1 (channel=$str2) for pending intent $str3"
+ })
+ }
+
+ fun logRemoteInputWasHandled(
+ entry: NotificationEntry?
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ }, {
+ " [Action click] Triggered remote input (for $str1))"
+ })
+ }
+
+ fun logStartingIntentWithDefaultHandler(
+ entry: NotificationEntry?,
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Launching intent $str2 via default handler (for $str1)"
+ })
+ }
+
+ fun logWaitingToCloseKeyguard(
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Intent $str1 launches an activity, dismissing keyguard first..."
+ })
+ }
+
+ fun logKeyguardGone(
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Keyguard dismissed, calling default handler for intent $str1"
+ })
+ }
+}
+
+private const val TAG = "ActionClickLogger"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index bf28040..9181c69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,7 +54,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.StatusBarModule;
+import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -114,6 +114,7 @@
private final SmartReplyController mSmartReplyController;
private final NotificationEntryManager mEntryManager;
private final Handler mMainHandler;
+ private final ActionClickLogger mLogger;
private final Lazy<StatusBar> mStatusBarLazy;
@@ -138,14 +139,18 @@
mStatusBarLazy.get().wakeUpIfDozing(SystemClock.uptimeMillis(), view,
"NOTIFICATION_CLICK");
+ final NotificationEntry entry = getNotificationForParent(view.getParent());
+ mLogger.logInitialClick(entry, pendingIntent);
+
if (handleRemoteInput(view, pendingIntent)) {
+ mLogger.logRemoteInputWasHandled(entry);
return true;
}
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
- logActionClick(view, pendingIntent);
+ logActionClick(view, entry, pendingIntent);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
@@ -158,11 +163,15 @@
Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
options.second.setLaunchWindowingMode(
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent);
return RemoteViews.startPendingIntent(view, pendingIntent, options);
});
}
- private void logActionClick(View view, PendingIntent actionIntent) {
+ private void logActionClick(
+ View view,
+ NotificationEntry entry,
+ PendingIntent actionIntent) {
Integer actionIndex = (Integer)
view.getTag(com.android.internal.R.id.notification_action_index_tag);
if (actionIndex == null) {
@@ -170,7 +179,7 @@
return;
}
ViewParent parent = view.getParent();
- StatusBarNotification statusBarNotification = getNotificationForParent(parent);
+ StatusBarNotification statusBarNotification = entry.getSbn();
if (statusBarNotification == null) {
Log.w(TAG, "Couldn't determine notification for click.");
return;
@@ -212,10 +221,10 @@
}
}
- private StatusBarNotification getNotificationForParent(ViewParent parent) {
+ private NotificationEntry getNotificationForParent(ViewParent parent) {
while (parent != null) {
if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent).getEntry().getSbn();
+ return ((ExpandableNotificationRow) parent).getEntry();
}
parent = parent.getParent();
}
@@ -255,7 +264,7 @@
};
/**
- * Injected constructor. See {@link StatusBarModule}.
+ * Injected constructor. See {@link StatusBarDependenciesModule}.
*/
public NotificationRemoteInputManager(
Context context,
@@ -265,13 +274,15 @@
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger logger) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
mEntryManager = notificationEntryManager;
mStatusBarLazy = statusBarLazy;
mMainHandler = mainHandler;
+ mLogger = logger;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index f0fed13..b08eb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationListener;
@@ -73,7 +74,8 @@
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger actionClickLogger) {
return new NotificationRemoteInputManager(
context,
lockscreenUserManager,
@@ -82,7 +84,8 @@
statusBarLazy,
statusBarStateController,
mainHandler,
- remoteInputUriController);
+ remoteInputUriController,
+ actionClickLogger);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 428de9e..669e6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -37,6 +37,7 @@
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -73,6 +74,7 @@
private View mPendingRemoteInputView;
private KeyguardManager mKeyguardManager;
private final CommandQueue mCommandQueue;
+ private final ActionClickLogger mActionClickLogger;
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
private Handler mMainHandler = new Handler();
@@ -87,7 +89,8 @@
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ActivityStarter activityStarter, ShadeController shadeController,
- CommandQueue commandQueue) {
+ CommandQueue commandQueue,
+ ActionClickLogger clickLogger) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mShadeController = shadeController;
@@ -101,6 +104,7 @@
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = commandQueue;
mCommandQueue.addCallback(this);
+ mActionClickLogger = clickLogger;
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mGroupManager = groupManager;
// Listen to onKeyguardShowingChanged in case a managed profile needs to be unlocked
@@ -304,9 +308,12 @@
NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
+ mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
mActivityStarter.dismissKeyguardThenExecute(() -> {
+ mActionClickLogger.logKeyguardGone(pendingIntent);
+
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 1117646..5a7dea4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -82,7 +82,8 @@
() -> mock(StatusBar.class),
mStateController,
Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController);
+ mRemoteInputUriController,
+ mock(ActionClickLogger.class));
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -256,17 +257,26 @@
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
- TestableNotificationRemoteInputManager(Context context,
+ TestableNotificationRemoteInputManager(
+ Context context,
NotificationLockscreenUserManager lockscreenUserManager,
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
- super(context, lockscreenUserManager, smartReplyController, notificationEntryManager,
- statusBarLazy, statusBarStateController, mainHandler,
- remoteInputUriController);
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger actionClickLogger) {
+ super(
+ context,
+ lockscreenUserManager,
+ smartReplyController,
+ notificationEntryManager,
+ statusBarLazy,
+ statusBarStateController,
+ mainHandler,
+ remoteInputUriController,
+ actionClickLogger);
}
public void setUpWithPresenterForTest(Callback callback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 22dc080..79507e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -92,7 +92,8 @@
mNotificationEntryManager, () -> mock(StatusBar.class),
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController);
+ mRemoteInputUriController,
+ mock(ActionClickLogger.class));
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index cd2c349..bf2bd38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -31,6 +31,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -73,7 +74,8 @@
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
- mActivityStarter, mShadeController, new CommandQueue(mContext)));
+ mActivityStarter, mShadeController, new CommandQueue(mContext),
+ mock(ActionClickLogger.class)));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4f5a02a..2a65b33 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -201,7 +201,6 @@
private SurfaceControl.DisplayConfig[] mDisplayConfigs;
private Spline mSystemBrightnessToNits;
private Spline mNitsToHalBrightness;
- private boolean mHalBrightnessSupport;
private DisplayDeviceConfig mDisplayDeviceConfig;
@@ -225,7 +224,6 @@
}
mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
- mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken);
mDisplayDeviceConfig = null;
// Defer configuration file loading
BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
@@ -717,11 +715,10 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
+ "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
try {
- // TODO: make it float
if (isHalBrightnessRangeSpecified()) {
brightness = displayBrightnessToHalBrightness(
- BrightnessSynchronizer.brightnessFloatToInt(getContext(),
- brightness));
+ BrightnessSynchronizer.brightnessFloatToIntRange(
+ getContext(), brightness));
}
if (mBacklight != null) {
mBacklight.setBrightness(brightness);
@@ -744,12 +741,13 @@
* Hal brightness space if the HAL brightness space has been provided via
* a display device configuration file.
*/
- private float displayBrightnessToHalBrightness(int brightness) {
+ private float displayBrightnessToHalBrightness(float brightness) {
if (!isHalBrightnessRangeSpecified()) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- if (brightness == 0) {
+ if (BrightnessSynchronizer.floatEquals(
+ brightness, PowerManager.BRIGHTNESS_OFF)) {
return PowerManager.BRIGHTNESS_OFF_FLOAT;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 549e336..9de95ab 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2208,8 +2208,12 @@
public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
enforceAccessPermission();
long token = Binder.clearCallingIdentity();
- HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
- Binder.restoreCallingIdentity(token);
+ try {
+ HdmiControlService.this.setHdmiCecVolumeControlEnabled(
+ isHdmiCecVolumeControlEnabled);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 91c849c..0598680 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6684,6 +6684,13 @@
}
@Override
+ void getAnimationPosition(Point outPosition) {
+ // Always animate from zero because if the activity doesn't fill the task, the letterbox
+ // will fill the remaining area that should be included in the animation.
+ outPosition.set(0, 0);
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newParentConfig) {
if (mCompatDisplayInsets != null) {
Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 70488b4..2f868d9 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1311,8 +1311,17 @@
/**
* Make sure that all activities that need to be visible in the stack (that is, they
* currently can be seen by the user) actually are and update their configuration.
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating
+ * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen as part of
+ * {@link mEnsureActivitiesVisibleHelper}.
+ *
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows) {
ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
}
@@ -1321,9 +1330,19 @@
* Ensure visibility with an option to also update the configuration of visible activities.
* @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ * @param notifyClients Flag indicating whether the visibility updates should be sent to the
+ * clients in {@link mEnsureActivitiesVisibleHelper}.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating
+ * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen as part of
+ * {@link mEnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a stack in most cases.
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mTopActivityOccludesKeyguard = false;
mTopDismissingKeyguardActivity = null;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 79e8ee3..bcdd6e3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1539,7 +1539,10 @@
*
* Note: This method should only be called from {@link #startActivityUnchecked}.
*/
- private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
+
+ // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
+ @VisibleForTesting
+ int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
boolean restrictedBgActivity) {
@@ -1660,7 +1663,10 @@
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisible(mStartActivity, 0, !PRESERVE_WINDOWS);
+ // Passing {@code null} as the start parameter ensures all activities are made
+ // visible.
+ mTargetStack.ensureActivitiesVisible(null /* starting */,
+ 0 /* configChanges */, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c92de2b..c4e03f5 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.ActivityStack.TAG_VISIBILITY;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
+import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -42,6 +43,16 @@
mContiner = container;
}
+ /**
+ * Update all attributes except {@link mContiner} to use in subsequent calculations.
+ *
+ * @param starting The activity that is being started
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+ * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+ * be sent to the clients.
+ */
void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
mStarting = starting;
@@ -60,8 +71,17 @@
* Ensure visibility with an option to also update the configuration of visible activities.
* @see ActivityStack#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ *
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+ * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+ * be sent to the clients.
*/
- void process(ActivityRecord starting, int configChanges, boolean preserveWindows,
+ void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
reset(starting, configChanges, preserveWindows, notifyClients);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5d7ec12..7bfddd7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2120,6 +2120,11 @@
return getBounds();
}
+ /** Gets the position relative to parent for animation. */
+ void getAnimationPosition(Point outPosition) {
+ getRelativePosition(outPosition);
+ }
+
/**
* Applies the app transition animation according the given the layout properties in the
* window hierarchy.
@@ -2178,9 +2183,9 @@
// Separate position and size for use in animators.
mTmpRect.set(getAnimationBounds(appStackClipMode));
- if (sHierarchicalAnimations) {
- getRelativePosition(mTmpPoint);
- } else {
+ getAnimationPosition(mTmpPoint);
+ if (!sHierarchicalAnimations) {
+ // Non-hierarchical animation uses position in global coordinates.
mTmpPoint.set(mTmpRect.left, mTmpRect.top);
}
mTmpRect.offsetTo(0, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index edc9756..02d1f9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -49,6 +49,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -56,10 +57,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -99,7 +100,6 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityStarterTests extends ActivityTestsBase {
- private ActivityStarter mStarter;
private ActivityStartController mController;
private ActivityMetricsLogger mActivityMetricsLogger;
private PackageManagerInternal mMockPackageManager;
@@ -127,8 +127,6 @@
mController = mock(ActivityStartController.class);
mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
clearInvocations(mActivityMetricsLogger);
- mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
- mock(ActivityStartInterceptor.class));
}
@Test
@@ -181,6 +179,7 @@
* {@link ActivityStarter#execute} based on these preconditions and ensures the result matches
* the expected. It is important to note that the method also checks side effects of the start,
* such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios.
+ *
* @param preconditions A bitmask representing the preconditions for the launch
* @param launchFlags The launch flags to be provided by the launch {@link Intent}.
* @param expectedResult The expected result from the launch.
@@ -202,7 +201,7 @@
final WindowProcessController wpc =
containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
? null : new WindowProcessController(service, ai, null, 0, -1, null, listener);
- doReturn(wpc).when(service).getProcessController(anyObject());
+ doReturn(wpc).when(service).getProcessController(any());
final Intent intent = new Intent();
intent.setFlags(launchFlags);
@@ -1034,4 +1033,46 @@
verify(recentTasks, times(1)).add(any());
}
+
+ @Test
+ public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() {
+ // Given
+ final ActivityStarter starter = prepareStarter(0, false);
+
+ starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed");
+
+ final ActivityRecord targetRecord = new ActivityBuilder(mService).build();
+ targetRecord.setFocusable(false);
+ targetRecord.setVisibility(false);
+ final ActivityRecord sourceRecord = new ActivityBuilder(mService).build();
+
+ final ActivityStack stack = spy(
+ mRootWindowContainer.getDefaultTaskDisplayArea()
+ .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+ /* onTop */true));
+
+ stack.addChild(targetRecord);
+
+ doReturn(stack).when(mRootWindowContainer)
+ .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
+
+ starter.mStartActivity = new ActivityBuilder(mService).build();
+
+ // When
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* restrictedBgActivity */false);
+
+ // Then
+ verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ verify(targetRecord).makeVisibleIfNeeded(null, true);
+ assertTrue(targetRecord.mVisibleRequested);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 8f18f64..07050d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
@@ -432,20 +433,35 @@
removeGlobalMinSizeRestriction();
final Rect stackBounds = new Rect(0, 0, 1000, 600);
final Rect taskBounds = new Rect(100, 400, 600, 800);
- mStack.setBounds(stackBounds);
- mTask.setBounds(taskBounds);
+ // Set the bounds and windowing mode to window configuration directly, otherwise the
+ // testing setups may be discarded by configuration resolving.
+ mStack.getWindowConfiguration().setBounds(stackBounds);
+ mTask.getWindowConfiguration().setBounds(taskBounds);
+ mActivity.getWindowConfiguration().setBounds(taskBounds);
// Check that anim bounds for freeform window match task bounds
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE));
// STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by
// bounds animation layer.
- mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ // Even the activity is smaller than task and it is not aligned to the top-left corner of
+ // task, the animation bounds the same as task and position should be zero because in real
+ // case the letterbox will fill the remaining area in task.
+ final Rect halfBounds = new Rect(taskBounds);
+ halfBounds.scale(0.5f);
+ mActivity.getWindowConfiguration().setBounds(halfBounds);
+ final Point animationPosition = new Point();
+ mActivity.getAnimationPosition(animationPosition);
+
+ assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ assertEquals(new Point(0, 0), animationPosition);
+
// STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
- mTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
}