summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--StubLibraries.bp2
-rw-r--r--api/Android.bp2
-rw-r--r--core/api/current.txt21
-rw-r--r--core/java/android/hardware/input/InputManager.java28
-rw-r--r--core/java/android/os/BadTypeParcelableException.java30
-rw-r--r--core/java/android/os/BaseBundle.java147
-rw-r--r--core/java/android/os/Bundle.java152
-rw-r--r--core/java/android/os/Parcel.java205
-rw-r--r--core/java/android/view/ImeFocusController.java10
-rw-r--r--core/java/android/view/InputDevice.java50
-rw-r--r--core/java/android/view/SurfaceControl.java4
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/view/WindowManager.java20
-rw-r--r--core/java/android/widget/TextView.java8
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialog.java49
-rw-r--r--core/java/com/android/internal/app/MediaRouteDialogPresenter.java25
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java9
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/layout/media_route_chooser_dialog.xml27
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--graphics/java/android/graphics/ColorSpace.java56
-rw-r--r--packages/CompanionDeviceManager/Android.bp1
-rw-r--r--packages/CompanionDeviceManager/AndroidManifest.xml1
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java28
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java13
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java54
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java48
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java30
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java97
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java60
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt6
-rw-r--r--packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml32
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_circle.xml8
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml8
-rw-r--r--packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml4
-rw-r--r--packages/SystemUI/res/layout/alert_dialog_systemui.xml10
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_container.xml31
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml96
-rw-r--r--packages/SystemUI/res/values/attrs.xml4
-rw-r--r--packages/SystemUI/res/values/colors.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/strings.xml13
-rw-r--r--packages/SystemUI/res/values/styles.xml12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java36
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java8
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java108
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java6
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodBindingController.java5
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java13
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java17
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java28
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java3
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java11
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java7
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java9
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java148
-rw-r--r--services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java114
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java84
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java84
-rw-r--r--services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java73
-rw-r--r--services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java103
-rw-r--r--services/core/java/com/android/server/vibrator/RampOffVibratorStep.java84
-rw-r--r--services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java186
-rw-r--r--services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java369
-rw-r--r--services/core/java/com/android/server/vibrator/Step.java103
-rw-r--r--services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java66
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java355
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java1462
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java45
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java4
-rw-r--r--services/core/java/com/android/server/wm/Transition.java12
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java265
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java80
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java22
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java7
-rw-r--r--telephony/java/android/telephony/UiccCardInfo.java15
-rw-r--r--telephony/java/android/telephony/UiccPortInfo.java4
-rw-r--r--telephony/java/android/telephony/UiccSlotInfo.java4
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java57
116 files changed, 3863 insertions, 2165 deletions
diff --git a/Android.bp b/Android.bp
index 8afea341be70..933d1aff842b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -591,7 +591,7 @@ stubs_defaults {
libs: [
"art.module.public.api",
"sdk_module-lib_current_framework-tethering",
- "sdk_module-lib_current_framework-connectivity-tiramisu",
+ "sdk_module-lib_current_framework-connectivity-t",
"sdk_public_current_framework-bluetooth",
// There are a few classes from modules used by the core that
// need to be resolved by metalava. We use a prebuilt stub of the
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 726ab2a70a5e..94f4374594ec 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -258,7 +258,7 @@ java_library {
srcs: [":module-lib-api-stubs-docs-non-updatable"],
libs: [
"sdk_module-lib_current_framework-tethering",
- "sdk_module-lib_current_framework-connectivity-tiramisu",
+ "sdk_module-lib_current_framework-connectivity-t",
"sdk_public_current_framework-bluetooth",
// NOTE: The below can be removed once the prebuilt stub contains bluetooth.
"sdk_system_current_android",
diff --git a/api/Android.bp b/api/Android.bp
index 66c7823ed416..bbe26b73c721 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -113,7 +113,7 @@ combined_apis {
"framework-appsearch",
"framework-bluetooth",
"framework-connectivity",
- "framework-connectivity-tiramisu",
+ "framework-connectivity-t",
"framework-graphics",
"framework-media",
"framework-mediaprovider",
diff --git a/core/api/current.txt b/core/api/current.txt
index ddfbb44e44a6..3a6fc590648e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14390,6 +14390,8 @@ package android.graphics {
method @NonNull @Size(min=3) public abstract float[] fromXyz(@NonNull @Size(min=3) float[]);
method @NonNull public static android.graphics.ColorSpace get(@NonNull android.graphics.ColorSpace.Named);
method @IntRange(from=1, to=4) public int getComponentCount();
+ method public int getDataSpace();
+ method @Nullable public static android.graphics.ColorSpace getFromDataSpace(int);
method @IntRange(from=android.graphics.ColorSpace.MIN_ID, to=android.graphics.ColorSpace.MAX_ID) public int getId();
method public abstract float getMaxValue(@IntRange(from=0, to=3) int);
method public abstract float getMinValue(@IntRange(from=0, to=3) int);
@@ -30684,7 +30686,7 @@ package android.os {
public class BaseBundle {
method public void clear();
method public boolean containsKey(String);
- method @Nullable public Object get(String);
+ method @Deprecated @Nullable public Object get(String);
method public boolean getBoolean(String);
method public boolean getBoolean(String, boolean);
method @Nullable public boolean[] getBooleanArray(@Nullable String);
@@ -30926,16 +30928,21 @@ package android.os {
method public float getFloat(String, float);
method @Nullable public float[] getFloatArray(@Nullable String);
method @Nullable public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(@Nullable String);
- method @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
- method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
- method @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
- method @Nullable public java.io.Serializable getSerializable(@Nullable String);
+ method @Deprecated @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
+ method @Nullable public <T> T getParcelable(@Nullable String, @NonNull Class<T>);
+ method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
+ method @Nullable public <T> T[] getParcelableArray(@Nullable String, @NonNull Class<T>);
+ method @Deprecated @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
+ method @Nullable public <T> java.util.ArrayList<T> getParcelableArrayList(@Nullable String, @NonNull Class<T>);
+ method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String);
+ method @Nullable public <T extends java.io.Serializable> T getSerializable(@Nullable String, @NonNull Class<T>);
method public short getShort(String);
method public short getShort(String, short);
method @Nullable public short[] getShortArray(@Nullable String);
method @Nullable public android.util.Size getSize(@Nullable String);
method @Nullable public android.util.SizeF getSizeF(@Nullable String);
- method @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+ method @Deprecated @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+ method @Nullable public <T> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String, @NonNull Class<T>);
method @Nullable public java.util.ArrayList<java.lang.String> getStringArrayList(@Nullable String);
method public boolean hasFileDescriptors();
method public void putAll(android.os.Bundle);
@@ -49285,7 +49292,7 @@ package android.view {
method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
- method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+ method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 2fd79cf980c7..c38a847dfb9f 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1079,16 +1079,24 @@ public final class InputManager {
}
/**
- * Gets the key code produced by the specified location on a US keyboard layout.
- * Key code as defined in {@link android.view.KeyEvent}.
- * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
- * which can alter their key mapping using country specific keyboard layouts.
- *
- * @param deviceId The input device id.
- * @param locationKeyCode The location of a key on a US keyboard layout.
- * @return The key code produced when pressing the key at the specified location, given the
- * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
- * mapping could not be determined, or if an error occurred.
+ * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+ * QWERTY keyboard layout.
+ * <p>
+ * This API is useful for querying the physical location of keys that change the character
+ * produced based on the current locale and keyboard layout.
+ * <p>
+ * @see InputDevice#getKeyCodeForKeyLocation(int) for examples.
+ *
+ * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+ * This provides a consistent way of referring to the physical location of a key independently
+ * of the current keyboard layout. Also see the
+ * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+ * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+ * physical location of a key.
+ * @return The key code produced by the key at the specified location, given the current
+ * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+ * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
+ *
* @hide
*/
public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java
new file mode 100644
index 000000000000..2ca3bd2adca1
--- /dev/null
+++ b/core/java/android/os/BadTypeParcelableException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.os;
+
+/** Used by Parcel to signal that the type on the payload was not expected by the caller. */
+class BadTypeParcelableException extends BadParcelableException {
+ BadTypeParcelableException(String msg) {
+ super(msg);
+ }
+ BadTypeParcelableException(Exception cause) {
+ super(cause);
+ }
+ BadTypeParcelableException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 244335d3aa06..45812e551618 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -16,6 +16,8 @@
package android.os;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -31,7 +33,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
/**
* A mapping from String keys to values of various types. In most cases, you
@@ -254,8 +256,8 @@ public class BaseBundle {
}
try {
return getValueAt(0, String.class);
- } catch (ClassCastException | BadParcelableException e) {
- typeWarning("getPairValue()", /* value */ null, "String", e);
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning("getPairValue()", "String", e);
return null;
}
}
@@ -320,28 +322,46 @@ public class BaseBundle {
* This call should always be made after {@link #unparcel()} or inside a lock after making sure
* {@code mMap} is not null.
*
+ * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in
+ * other deprecated APIs.
+ *
* @hide
*/
+ @Deprecated
+ @Nullable
final Object getValue(String key) {
return getValue(key, /* clazz */ null);
}
+ /** Same as {@link #getValue(String, Class, Class[])} with no item types. */
+ @Nullable
+ final <T> T getValue(String key, @Nullable Class<T> clazz) {
+ // Avoids allocating Class[0] array
+ return getValue(key, clazz, (Class<?>[]) null);
+ }
+
/**
- * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code
+ * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code
* null} for no type check).
*
+ * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
+ *
* This call should always be made after {@link #unparcel()} or inside a lock after making sure
* {@code mMap} is not null.
*
* @hide
*/
- final <T> T getValue(String key, @Nullable Class<T> clazz) {
+ @Nullable
+ final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
int i = mMap.indexOfKey(key);
- return (i >= 0) ? getValueAt(i, clazz) : null;
+ return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null;
}
/**
- * Returns the value for a certain position in the array map.
+ * Returns the value for a certain position in the array map for expected return type {@code
+ * clazz} (or pass {@code null} for no type check).
+ *
+ * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
*
* This call should always be made after {@link #unparcel()} or inside a lock after making sure
* {@code mMap} is not null.
@@ -349,11 +369,12 @@ public class BaseBundle {
* @hide
*/
@SuppressWarnings("unchecked")
- final <T> T getValueAt(int i, @Nullable Class<T> clazz) {
+ @Nullable
+ final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
Object object = mMap.valueAt(i);
- if (object instanceof Function<?, ?>) {
+ if (object instanceof BiFunction<?, ?, ?>) {
try {
- object = ((Function<Class<?>, ?>) object).apply(clazz);
+ object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
@@ -615,7 +636,11 @@ public class BaseBundle {
*
* @param key a String key
* @return an Object, or null
+ *
+ * @deprecated Use the type-safe specific APIs depending on the type of the item to be
+ * retrieved, eg. {@link #getString(String)}.
*/
+ @Deprecated
@Nullable
public Object get(String key) {
unparcel();
@@ -623,6 +648,32 @@ public class BaseBundle {
}
/**
+ * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <p>Use the more specific APIs where possible, especially in the case of containers such as
+ * lists, since those APIs allow you to specify the type of the items.
+ *
+ * @param key String key
+ * @param clazz The type of the object expected
+ * @return an Object, or null
+ */
+ @Nullable
+ <T> T get(@Nullable String key, @NonNull Class<T> clazz) {
+ unparcel();
+ try {
+ return getValue(key, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, clazz.getCanonicalName(), e);
+ return null;
+ }
+ }
+
+ /**
* Removes any entry with the given key from the mapping of this Bundle.
*
* @param key a String key
@@ -1006,7 +1057,7 @@ public class BaseBundle {
sb.append(" but value was a ");
sb.append(value.getClass().getName());
} else {
- sb.append(" but value was of a different type ");
+ sb.append(" but value was of a different type");
}
sb.append(". The default value ");
sb.append(defaultValue);
@@ -1019,6 +1070,10 @@ public class BaseBundle {
typeWarning(key, value, className, "<null>", e);
}
+ void typeWarning(String key, String className, RuntimeException e) {
+ typeWarning(key, /* value */ null, className, "<null>", e);
+ }
+
/**
* Returns the value associated with the given key, or defaultValue if
* no mapping of the desired type exists for the given key.
@@ -1358,7 +1413,11 @@ public class BaseBundle {
*
* @param key a String, or null
* @return a Serializable value, or null
+ *
+ * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in
+ * other deprecated APIs.
*/
+ @Deprecated
@Nullable
Serializable getSerializable(@Nullable String key) {
unparcel();
@@ -1375,6 +1434,36 @@ public class BaseBundle {
}
/**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * @param key a String, or null
+ * @param clazz The expected class of the returned type
+ * @return a Serializable value, or null
+ */
+ @Nullable
+ <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) {
+ return get(key, clazz);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+ unparcel();
+ try {
+ return getValue(key, ArrayList.class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e);
+ return null;
+ }
+ }
+
+ /**
* Returns the value associated with the given key, or null if
* no mapping of the desired type exists for the given key or a null
* value is explicitly associated with the key.
@@ -1384,17 +1473,7 @@ public class BaseBundle {
*/
@Nullable
ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
- unparcel();
- Object o = getValue(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<Integer>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<Integer>", e);
- return null;
- }
+ return getArrayList(key, Integer.class);
}
/**
@@ -1407,17 +1486,7 @@ public class BaseBundle {
*/
@Nullable
ArrayList<String> getStringArrayList(@Nullable String key) {
- unparcel();
- Object o = getValue(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<String>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<String>", e);
- return null;
- }
+ return getArrayList(key, String.class);
}
/**
@@ -1430,17 +1499,7 @@ public class BaseBundle {
*/
@Nullable
ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
- unparcel();
- Object o = getValue(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<CharSequence>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<CharSequence>", e);
- return null;
- }
+ return getArrayList(key, CharSequence.class);
}
/**
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 2b13f20ffe6d..edbbb59ac77d 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Size;
@@ -876,7 +877,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public Bundle getBundle(@Nullable String key) {
unparcel();
- Object o = getValue(key);
+ Object o = mMap.get(key);
if (o == null) {
return null;
}
@@ -899,7 +900,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*
* @param key a String, or {@code null}
* @return a Parcelable value, or {@code null}
+ *
+ * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
*/
+ @Deprecated
@Nullable
public <T extends Parcelable> T getParcelable(@Nullable String key) {
unparcel();
@@ -916,30 +921,28 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
- * Returns the value associated with the given key, or {@code null} if
- * no mapping of the desired type exists for the given key or a {@code null}
- * value is explicitly associated with the key.
+ * Returns the value associated with the given key or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
*
* <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
* you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
* Otherwise, this method might throw an exception or return {@code null}.
*
* @param key a String, or {@code null}
- * @param clazz The type of the object expected or {@code null} for performing no checks.
+ * @param clazz The type of the object expected
* @return a Parcelable value, or {@code null}
- *
- * @hide
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
- unparcel();
- try {
- return getValue(key, requireNonNull(clazz));
- } catch (ClassCastException | BadParcelableException e) {
- typeWarning(key, /* value */ null, "Parcelable", e);
- return null;
- }
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ return get(key, clazz);
}
/**
@@ -953,7 +956,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*
* @param key a String, or {@code null}
* @return a Parcelable[] value, or {@code null}
+ *
+ * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from
+ * Android {@link Build.VERSION_CODES#TIRAMISU}.
*/
+ @Deprecated
@Nullable
public Parcelable[] getParcelableArray(@Nullable String key) {
unparcel();
@@ -970,6 +977,39 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+ * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+ * Otherwise, this method might throw an exception or return {@code null}.
+ *
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array
+ * @return a Parcelable[] value, or {@code null}
+ */
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ unparcel();
+ try {
+ // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting.
+ return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, clazz.getCanonicalName() + "[]", e);
+ return null;
+ }
+ }
+
+ /**
* Returns the value associated with the given key, or {@code null} if
* no mapping of the desired type exists for the given key or a {@code null}
* value is explicitly associated with the key.
@@ -980,7 +1020,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*
* @param key a String, or {@code null}
* @return an ArrayList<T> value, or {@code null}
+ *
+ * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
*/
+ @Deprecated
@Nullable
public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
unparcel();
@@ -997,14 +1041,43 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+ * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+ * Otherwise, this method might throw an exception or return {@code null}.
+ *
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array list
+ * @return an ArrayList<T> value, or {@code null}
+ */
+ @SuppressLint("NullableCollection")
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> ArrayList<T> getParcelableArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ return getArrayList(key, clazz);
+ }
+
+ /**
* Returns the value associated with the given key, or null if
* no mapping of the desired type exists for the given key or a null
* value is explicitly associated with the key.
*
* @param key a String, or null
- *
* @return a SparseArray of T values, or null
+ *
+ * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from
+ * Android {@link Build.VERSION_CODES#TIRAMISU}.
*/
+ @Deprecated
@Nullable
public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
unparcel();
@@ -1021,13 +1094,44 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * @param key a String, or null
+ * @return a SparseArray of T values, or null
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key,
+ @NonNull Class<T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ unparcel();
+ try {
+ return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e);
+ return null;
+ }
+ }
+
+ /**
* Returns the value associated with the given key, or null if
* no mapping of the desired type exists for the given key or a null
* value is explicitly associated with the key.
*
* @param key a String, or null
* @return a Serializable value, or null
+ *
+ * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
*/
+ @Deprecated
@Override
@Nullable
public Serializable getSerializable(@Nullable String key) {
@@ -1035,6 +1139,24 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * @param key a String, or null
+ * @param clazz The expected class of the returned type
+ * @return a Serializable value, or null
+ */
+ @Nullable
+ public <T extends Serializable> T getSerializable(@Nullable String key,
+ @NonNull Class<T> clazz) {
+ return super.getSerializable(key, requireNonNull(clazz));
+ }
+
+ /**
* Returns the value associated with the given key, or null if
* no mapping of the desired type exists for the given key or a null
* value is explicitly associated with the key.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ae923530b651..09cfb6e70cba 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,8 @@
package android.os;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
@@ -65,6 +67,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
@@ -287,26 +290,26 @@ public final class Parcel {
private static final int VAL_NULL = -1;
private static final int VAL_STRING = 0;
private static final int VAL_INTEGER = 1;
- private static final int VAL_MAP = 2;
+ private static final int VAL_MAP = 2; // length-prefixed
private static final int VAL_BUNDLE = 3;
- private static final int VAL_PARCELABLE = 4;
+ private static final int VAL_PARCELABLE = 4; // length-prefixed
private static final int VAL_SHORT = 5;
private static final int VAL_LONG = 6;
private static final int VAL_FLOAT = 7;
private static final int VAL_DOUBLE = 8;
private static final int VAL_BOOLEAN = 9;
private static final int VAL_CHARSEQUENCE = 10;
- private static final int VAL_LIST = 11;
- private static final int VAL_SPARSEARRAY = 12;
+ private static final int VAL_LIST = 11; // length-prefixed
+ private static final int VAL_SPARSEARRAY = 12; // length-prefixed
private static final int VAL_BYTEARRAY = 13;
private static final int VAL_STRINGARRAY = 14;
private static final int VAL_IBINDER = 15;
- private static final int VAL_PARCELABLEARRAY = 16;
- private static final int VAL_OBJECTARRAY = 17;
+ private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed
+ private static final int VAL_OBJECTARRAY = 17; // length-prefixed
private static final int VAL_INTARRAY = 18;
private static final int VAL_LONGARRAY = 19;
private static final int VAL_BYTE = 20;
- private static final int VAL_SERIALIZABLE = 21;
+ private static final int VAL_SERIALIZABLE = 21; // length-prefixed
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
@@ -3179,8 +3182,7 @@ public final class Parcel {
*/
@Deprecated
public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
- int n = readInt();
- readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null);
+ readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null);
}
/**
@@ -3195,8 +3197,7 @@ public final class Parcel {
@NonNull Class<V> clazzValue) {
Objects.requireNonNull(clazzKey);
Objects.requireNonNull(clazzValue);
- int n = readInt();
- readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+ readMapInternal(outVal, loader, clazzKey, clazzValue);
}
/**
@@ -3245,13 +3246,7 @@ public final class Parcel {
@Deprecated
@Nullable
public HashMap readHashMap(@Nullable ClassLoader loader) {
- int n = readInt();
- if (n < 0) {
- return null;
- }
- HashMap m = new HashMap(n);
- readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null);
- return m;
+ return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null);
}
/**
@@ -3267,13 +3262,7 @@ public final class Parcel {
@NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
Objects.requireNonNull(clazzKey);
Objects.requireNonNull(clazzValue);
- int n = readInt();
- if (n < 0) {
- return null;
- }
- HashMap<K, V> map = new HashMap<>(n);
- readMapInternal(map, n, loader, clazzKey, clazzValue);
- return map;
+ return readHashMapInternal(loader, clazzKey, clazzValue);
}
/**
@@ -4296,16 +4285,17 @@ public final class Parcel {
/**
- * @param clazz The type of the object expected or {@code null} for performing no checks.
+ * @see #readValue(int, ClassLoader, Class, Class[])
*/
@Nullable
- private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
+ @Nullable Class<?>... itemTypes) {
int type = readInt();
final T object;
if (isLengthPrefixed(type)) {
int length = readInt();
int start = dataPosition();
- object = readValue(type, loader, clazz);
+ object = readValue(type, loader, clazz, itemTypes);
int actual = dataPosition() - start;
if (actual != length) {
Slog.wtfStack(TAG,
@@ -4313,25 +4303,26 @@ public final class Parcel {
+ " consumed " + actual + " bytes, but " + length + " expected.");
}
} else {
- object = readValue(type, loader, clazz);
+ object = readValue(type, loader, clazz, itemTypes);
}
return object;
}
/**
- * This will return a {@link Function} for length-prefixed types that deserializes the object
- * when {@link Function#apply} is called with the expected class of the return object (or {@code
- * null} for no type check), for other types it will return the object itself.
+ * This will return a {@link BiFunction} for length-prefixed types that deserializes the object
+ * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link
+ * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it
+ * will return the object itself.
*
- * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that
- * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No
+ * <p>After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you
+ * shouldn't recycle the parcel, not at least until all objects have been retrieved. No
* synchronization attempts are made.
*
* </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
* function objects are equal if either of the following is true:
* <ul>
- * <li>{@link Function#apply} has been called on both and both objects returned are equal.
- * <li>{@link Function#apply} hasn't been called on either one and everything below is true:
+ * <li>{@link BiFunction#apply} has been called on both and both objects returned are equal.
+ * <li>{@link BiFunction#apply} hasn't been called on either one and everything below is true:
* <ul>
* <li>The {@code loader} parameters used to retrieve each are equal.
* <li>They both have the same type.
@@ -4358,7 +4349,7 @@ public final class Parcel {
}
- private static final class LazyValue implements Function<Class<?>, Object> {
+ private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
/**
* | 4B | 4B |
* mSource = Parcel{... | type | length | object | ...}
@@ -4390,7 +4381,7 @@ public final class Parcel {
}
@Override
- public Object apply(@Nullable Class<?> clazz) {
+ public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
Parcel source = mSource;
if (source != null) {
synchronized (source) {
@@ -4399,7 +4390,7 @@ public final class Parcel {
int restore = source.dataPosition();
try {
source.setDataPosition(mPosition);
- mObject = source.readValue(mLoader, clazz);
+ mObject = source.readValue(mLoader, clazz, itemTypes);
} finally {
source.setDataPosition(restore);
}
@@ -4479,14 +4470,25 @@ public final class Parcel {
}
}
+ /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
+ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ // Avoids allocating Class[0] array
+ return readValue(type, loader, clazz, (Class<?>[]) null);
+ }
+
/**
* Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
* type first.
+ *
* @param clazz The type of the object expected or {@code null} for performing no checks.
+ * @param itemTypes If the value is a container, these represent the item types (eg. for a list
+ * it's the item type, for a map, it's the key type, followed by the value
+ * type).
*/
@SuppressWarnings("unchecked")
@Nullable
- private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz,
+ @Nullable Class<?>... itemTypes) {
final Object object;
switch (type) {
case VAL_NULL:
@@ -4502,7 +4504,11 @@ public final class Parcel {
break;
case VAL_MAP:
- object = readHashMap(loader);
+ checkTypeToUnparcel(clazz, HashMap.class);
+ Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0);
+ Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1);
+ checkArgument((keyType == null) == (valueType == null));
+ object = readHashMapInternal(loader, keyType, valueType);
break;
case VAL_PARCELABLE:
@@ -4533,10 +4539,12 @@ public final class Parcel {
object = readCharSequence();
break;
- case VAL_LIST:
- object = readArrayList(loader);
+ case VAL_LIST: {
+ checkTypeToUnparcel(clazz, ArrayList.class);
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ object = readArrayListInternal(loader, itemType);
break;
-
+ }
case VAL_BOOLEANARRAY:
object = createBooleanArray();
break;
@@ -4557,10 +4565,12 @@ public final class Parcel {
object = readStrongBinder();
break;
- case VAL_OBJECTARRAY:
- object = readArray(loader);
+ case VAL_OBJECTARRAY: {
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class);
+ object = readArrayInternal(loader, itemType);
break;
-
+ }
case VAL_INTARRAY:
object = createIntArray();
break;
@@ -4577,14 +4587,18 @@ public final class Parcel {
object = readSerializableInternal(loader, clazz);
break;
- case VAL_PARCELABLEARRAY:
- object = readParcelableArray(loader);
+ case VAL_PARCELABLEARRAY: {
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class);
+ object = readParcelableArrayInternal(loader, itemType);
break;
-
- case VAL_SPARSEARRAY:
- object = readSparseArray(loader);
+ }
+ case VAL_SPARSEARRAY: {
+ checkTypeToUnparcel(clazz, SparseArray.class);
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ object = readSparseArrayInternal(loader, itemType);
break;
-
+ }
case VAL_SPARSEBOOLEANARRAY:
object = readSparseBooleanArray();
break;
@@ -4632,7 +4646,7 @@ public final class Parcel {
+ " at offset " + off);
}
if (object != null && clazz != null && !clazz.isInstance(object)) {
- throw new BadParcelableException("Unparcelled object " + object
+ throw new BadTypeParcelableException("Unparcelled object " + object
+ " is not an instance of required class " + clazz.getName()
+ " provided in the parameter");
}
@@ -4659,6 +4673,38 @@ public final class Parcel {
}
/**
+ * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of
+ * {@code requiredArrayType}.
+ */
+ private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType,
+ Class<?> componentTypeToUnparcel) {
+ if (requiredArrayType != null) {
+ // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check
+ Class<?> requiredComponentType = requiredArrayType.getComponentType();
+ if (requiredComponentType == null) {
+ throw new BadTypeParcelableException(
+ "About to unparcel an array but type "
+ + requiredArrayType.getCanonicalName()
+ + " required by caller is not an array.");
+ }
+ checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel);
+ }
+ }
+
+ /**
+ * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code
+ * requiredType} is not {@code null}.
+ */
+ private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) {
+ if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) {
+ throw new BadTypeParcelableException(
+ "About to unparcel a " + typeToUnparcel.getCanonicalName()
+ + ", which is not a subtype of type " + requiredType.getCanonicalName()
+ + " required by caller.");
+ }
+ }
+
+ /**
* Read and return a new Parcelable from the parcel. The given class loader
* will be used to load any enclosed Parcelables. If it is null, the default
* class loader will be used.
@@ -4788,7 +4834,7 @@ public final class Parcel {
if (clazz != null) {
Class<?> parcelableClass = creator.getClass().getEnclosingClass();
if (!clazz.isAssignableFrom(parcelableClass)) {
- throw new BadParcelableException("Parcelable creator " + name + " is not "
+ throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
+ "a subclass of required class " + clazz.getName()
+ " provided in the parameter");
}
@@ -4811,7 +4857,7 @@ public final class Parcel {
}
if (clazz != null) {
if (!clazz.isAssignableFrom(parcelableClass)) {
- throw new BadParcelableException("Parcelable creator " + name + " is not "
+ throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
+ "a subclass of required class " + clazz.getName()
+ " provided in the parameter");
}
@@ -4872,15 +4918,7 @@ public final class Parcel {
@Deprecated
@Nullable
public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
- int N = readInt();
- if (N < 0) {
- return null;
- }
- Parcelable[] p = new Parcelable[N];
- for (int i = 0; i < N; i++) {
- p[i] = readParcelable(loader);
- }
- return p;
+ return readParcelableArrayInternal(loader, /* clazz */ null);
}
/**
@@ -4892,14 +4930,20 @@ public final class Parcel {
* trying to instantiate an element.
*/
@SuppressLint({"ArrayReturn", "NullableCollection"})
- @SuppressWarnings("unchecked")
@Nullable
public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ return readParcelableArrayInternal(loader, requireNonNull(clazz));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader,
+ @Nullable Class<T> clazz) {
int n = readInt();
if (n < 0) {
return null;
}
- T[] p = (T[]) Array.newInstance(clazz, n);
+ T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n));
for (int i = 0; i < n; i++) {
p[i] = readParcelableInternal(loader, clazz);
}
@@ -4962,7 +5006,7 @@ public final class Parcel {
// the class the same way as ObjectInputStream, using the provided classloader.
Class<?> cl = Class.forName(name, false, loader);
if (!clazz.isAssignableFrom(cl)) {
- throw new BadParcelableException("Serializable object "
+ throw new BadTypeParcelableException("Serializable object "
+ cl.getName() + " is not a subclass of required class "
+ clazz.getName() + " provided in the parameter");
}
@@ -4987,7 +5031,7 @@ public final class Parcel {
// the deserialized object, as we cannot resolve the class the same way as
// ObjectInputStream.
if (!clazz.isAssignableFrom(object.getClass())) {
- throw new BadParcelableException("Serializable object "
+ throw new BadTypeParcelableException("Serializable object "
+ object.getClass().getName() + " is not a subclass of required class "
+ clazz.getName() + " provided in the parameter");
}
@@ -5097,7 +5141,26 @@ public final class Parcel {
readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null);
}
- /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
+ @Nullable
+ private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader,
+ @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ HashMap<K, V> map = new HashMap<>(n);
+ readMapInternal(map, n, loader, clazzKey, clazzValue);
+ return map;
+ }
+
+ private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal,
+ @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
+ @Nullable Class<V> clazzValue) {
+ int n = readInt();
+ readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+ }
+
+ private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
@Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
@Nullable Class<V> clazzValue) {
while (n > 0) {
@@ -5108,7 +5171,7 @@ public final class Parcel {
}
}
- /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
+ private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
int size, @Nullable ClassLoader loader) {
readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 3fc9f6bcbdeb..b48b5258237f 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -54,13 +54,11 @@ public final class ImeFocusController {
@NonNull
private InputMethodManagerDelegate getImmDelegate() {
- InputMethodManagerDelegate delegate = mDelegate;
- if (delegate != null) {
- return delegate;
+ if (mDelegate == null) {
+ mDelegate = mViewRootImpl.mContext.getSystemService(
+ InputMethodManager.class).getDelegate();
}
- delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
- mDelegate = delegate;
- return delegate;
+ return mDelegate;
}
/** Called when the view root is moved to a different display. */
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 188d7459f9a7..7d5603994efa 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -737,15 +737,47 @@ public final class InputDevice implements Parcelable {
}
/**
- * Gets the key code produced by the specified location on a US keyboard layout.
- * Key code as defined in {@link android.view.KeyEvent}.
- * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
- * which can alter their key mapping using country specific keyboard layouts.
- *
- * @param locationKeyCode The location of a key on a US keyboard layout.
- * @return The key code produced when pressing the key at the specified location, given the
- * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
- * mapping could not be determined, or if an error occurred.
+ * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+ * QWERTY keyboard layout.
+ * <p>
+ * This API is useful for querying the physical location of keys that change the character
+ * produced based on the current locale and keyboard layout.
+ * <p>
+ * The following table provides a non-exhaustive list of examples:
+ * <table border="2" width="85%" align="center" cellpadding="5">
+ * <thead>
+ * <tr><th>Active Keyboard Layout</th> <th>Input Parameter</th>
+ * <th>Return Value</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr>
+ * <td>French AZERTY</td>
+ * <td><code>{@link KeyEvent#KEYCODE_Q}</code></td>
+ * <td><code>{@link KeyEvent#KEYCODE_A}</code></td>
+ * </tr>
+ * <tr>
+ * <td>German QWERTZ</td>
+ * <td><code>{@link KeyEvent#KEYCODE_Y}</code></td>
+ * <td><code>{@link KeyEvent#KEYCODE_Z}</code></td>
+ * </tr>
+ * <tr>
+ * <td>US QWERTY</td>
+ * <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+ * <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+ * This provides a consistent way of referring to the physical location of a key independently
+ * of the current keyboard layout. Also see the
+ * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+ * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+ * physical location of a key.
+ * @return The key code produced by the key at the specified location, given the current
+ * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+ * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
*/
public int getKeyCodeForKeyLocation(int locationKeyCode) {
return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 98cef95885bd..632af2315bcd 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3094,6 +3094,10 @@ public final class SurfaceControl implements Parcelable {
* @param destFrame The destination rectangle in parent space. Or null for the source frame.
* @param orientation The buffer rotation
* @return This transaction object.
+ * @deprecated Use {@link #setCrop(SurfaceControl, Rect)},
+ * {@link #setBufferTransform(SurfaceControl, int)},
+ * {@link #setPosition(SurfaceControl, float, float)} and
+ * {@link #setScale(SurfaceControl, float, float)} instead.
*/
@NonNull
public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 286b502791c8..34a13868f4d7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14173,7 +14173,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
&& isAccessibilityPane()) {
// If the pane isn't visible, content changed events are sufficient unless we're
// reporting that the view just disappeared
- if ((getVisibility() == VISIBLE)
+ if ((isAggregatedVisible())
|| (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) {
final AccessibilityEvent event = AccessibilityEvent.obtain();
onInitializeAccessibilityEvent(event);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1c2704632e51..8ee3e432cbbb 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3222,7 +3222,8 @@ public interface WindowManager extends ViewManager {
/**
* The window is allowed to extend into the {@link DisplayCutout} area, only if the
- * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is
+ * {@link DisplayCutout} is fully contained within a system bar or the {@link DisplayCutout}
+ * is not deeper than 16 dp, but this depends on the OEM choice. Otherwise, the window is
* laid out such that it does not overlap with the {@link DisplayCutout} area.
*
* <p>
@@ -3237,6 +3238,13 @@ public interface WindowManager extends ViewManager {
* The usual precautions for not overlapping with the status and navigation bar are
* sufficient for ensuring that no important content overlaps with the DisplayCutout.
*
+ * <p>
+ * Note: OEMs can have an option to allow the window to always extend into the
+ * {@link DisplayCutout} area, no matter the cutout flag set, when the {@link DisplayCutout}
+ * is on the different side from system bars, only if the {@link DisplayCutout} overlaps at
+ * most 16dp with the windows.
+ * In such case, OEMs must provide an opt-in/out affordance for users.
+ *
* @see DisplayCutout
* @see WindowInsets
* @see #layoutInDisplayCutoutMode
@@ -3249,8 +3257,16 @@ public interface WindowManager extends ViewManager {
* The window is always allowed to extend into the {@link DisplayCutout} areas on the short
* edges of the screen.
*
+ * <p>
* The window will never extend into a {@link DisplayCutout} area on the long edges of the
- * screen.
+ * screen, unless the {@link DisplayCutout} is not deeper than 16 dp, but this depends on
+ * the OEM choice.
+ *
+ * <p>
+ * Note: OEMs can have an option to allow the window to extend into the
+ * {@link DisplayCutout} area on the long edge side, only if the cutout overlaps at most
+ * 16dp with the windows. In such case, OEMs must provide an opt-in/out affordance for
+ * users.
*
* <p>
* The window must make sure that no important content overlaps with the
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3dfb4a5a084a..c207af53fab7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12774,7 +12774,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Called when a context menu option for the text view is selected. Currently
* this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
- * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
+ * {@link android.R.id#copy}, {@link android.R.id#paste},
+ * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
+ * {@link android.R.id#shareText}.
*
* @return true if the context menu item action was performed.
*/
@@ -12965,6 +12967,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* method. The default actions can also be removed from the menu using
* {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
* {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
+ * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
* {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
*
* <p>Returning false from
@@ -13003,7 +13006,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
* android.view.Menu)} method. The default actions can also be removed from the menu using
* {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
- * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
+ * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
+ * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
*
* <p>Returning false from
* {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
index 7108d1443d3a..23d966fdbc0d 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -16,25 +16,26 @@
package com.android.internal.app;
-import com.android.internal.R;
-
-import android.app.Dialog;
+import android.app.AlertDialog;
import android.content.Context;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
+import com.android.internal.R;
+
import java.util.Comparator;
/**
@@ -48,9 +49,10 @@ import java.util.Comparator;
*
* TODO: Move this back into the API, as in the support library media router.
*/
-public class MediaRouteChooserDialog extends Dialog {
+public class MediaRouteChooserDialog extends AlertDialog {
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
+ private final boolean mShowProgressBarWhenEmpty;
private int mRouteTypes;
private View.OnClickListener mExtendedSettingsClickListener;
@@ -60,10 +62,15 @@ public class MediaRouteChooserDialog extends Dialog {
private boolean mAttachedToWindow;
public MediaRouteChooserDialog(Context context, int theme) {
+ this(context, theme, true);
+ }
+
+ public MediaRouteChooserDialog(Context context, int theme, boolean showProgressBarWhenEmpty) {
super(context, theme);
mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mCallback = new MediaRouterCallback();
+ mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
}
/**
@@ -120,28 +127,38 @@ public class MediaRouteChooserDialog extends Dialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ // Note: setView must be called before super.onCreate().
+ setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog,
+ null));
- getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
-
- setContentView(R.layout.media_route_chooser_dialog);
setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
? R.string.media_route_chooser_title_for_remote_display
: R.string.media_route_chooser_title);
- // Must be called after setContentView.
- getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
- isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light
- : R.drawable.ic_media_route_off_holo_dark);
+ setIcon(isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light
+ : R.drawable.ic_media_route_off_holo_dark);
+
+ super.onCreate(savedInstanceState);
+ View emptyView = findViewById(android.R.id.empty);
mAdapter = new RouteAdapter(getContext());
- mListView = (ListView)findViewById(R.id.media_route_list);
+ mListView = (ListView) findViewById(R.id.media_route_list);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(mAdapter);
- mListView.setEmptyView(findViewById(android.R.id.empty));
+ mListView.setEmptyView(emptyView);
- mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button);
+ mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button);
updateExtendedSettingsButton();
+
+ if (!mShowProgressBarWhenEmpty) {
+ findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
+
+ // Center the empty view when the progress bar is not shown.
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) emptyView.getLayoutParams();
+ params.gravity = Gravity.CENTER;
+ emptyView.setLayoutParams(params);
+ }
}
private void updateExtendedSettingsButton() {
diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
index bb2d7fab9cc1..5628b7ed9d15 100644
--- a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
+++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
@@ -66,24 +66,37 @@ public abstract class MediaRouteDialogPresenter {
}
}
+ /** Create a media route dialog as appropriate. */
public static Dialog createDialog(Context context,
int routeTypes, View.OnClickListener extendedSettingsClickListener) {
- final MediaRouter router = (MediaRouter)context.getSystemService(
- Context.MEDIA_ROUTER_SERVICE);
-
int theme = MediaRouteChooserDialog.isLightTheme(context)
? android.R.style.Theme_DeviceDefault_Light_Dialog
: android.R.style.Theme_DeviceDefault_Dialog;
+ return createDialog(context, routeTypes, extendedSettingsClickListener, theme);
+ }
+
+ /** Create a media route dialog as appropriate. */
+ public static Dialog createDialog(Context context,
+ int routeTypes, View.OnClickListener extendedSettingsClickListener, int theme) {
+ return createDialog(context, routeTypes, extendedSettingsClickListener, theme,
+ true /* showProgressBarWhenEmpty */);
+ }
+
+ /** Create a media route dialog as appropriate. */
+ public static Dialog createDialog(Context context, int routeTypes,
+ View.OnClickListener extendedSettingsClickListener, int theme,
+ boolean showProgressBarWhenEmpty) {
+ final MediaRouter router = context.getSystemService(MediaRouter.class);
MediaRouter.RouteInfo route = router.getSelectedRoute();
if (route.isDefault() || !route.matchesTypes(routeTypes)) {
- final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme);
+ final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme,
+ showProgressBarWhenEmpty);
d.setRouteTypes(routeTypes);
d.setExtendedSettingsClickListener(extendedSettingsClickListener);
return d;
} else {
- MediaRouteControllerDialog d = new MediaRouteControllerDialog(context, theme);
- return d;
+ return new MediaRouteControllerDialog(context, theme);
}
}
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 9bdcddf38c61..1fd04109ae45 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -888,6 +888,15 @@ public class ArrayUtils {
}
}
+ /**
+ * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code
+ * null}, otherwise returns {@code null}.
+ */
+ @Nullable
+ public static <T> T getOrNull(@Nullable T[] items, int i) {
+ return (items != null && items.length > i) ? items[i] : null;
+ }
+
public static @Nullable <T> T firstOrNull(T[] items) {
return items.length > 0 ? items[0] : null;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d4c03e412fcb..80317b8f5305 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6912,6 +6912,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.appsearch.contactsindexer.ContactsIndexerMaintenanceService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
index cd1c74fd7d7d..bf73f4b26c9c 100644
--- a/core/res/res/layout/media_route_chooser_dialog.xml
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -28,20 +28,21 @@
<!-- Content to show when list is empty. -->
<LinearLayout android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:visibility="gone">
- <ProgressBar android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:visibility="gone">
+ <ProgressBar android:id="@+id/media_route_progress_bar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
<TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingStart="16dp"
- android:text="@string/media_route_chooser_searching" />
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingStart="16dp"
+ android:text="@string/media_route_chooser_searching" />
</LinearLayout>
<!-- Settings button. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e7eeecc2b516..d4513d0c4a3d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1676,6 +1676,7 @@
<java-symbol type="id" name="media_route_volume_slider" />
<java-symbol type="id" name="media_route_control_frame" />
<java-symbol type="id" name="media_route_extended_settings_button" />
+ <java-symbol type="id" name="media_route_progress_bar" />
<java-symbol type="string" name="media_route_chooser_title" />
<java-symbol type="string" name="media_route_chooser_title_for_remote_display" />
<java-symbol type="string" name="media_route_controller_disconnect" />
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2f978fc1fc2d..582488ff8de3 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -22,6 +22,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
+import android.util.SparseIntArray;
import libcore.util.NativeAllocationRegistry;
@@ -207,6 +211,7 @@ public abstract class ColorSpace {
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+ private static final SparseIntArray sDataToColorSpaces = new SparseIntArray();
@NonNull private final String mName;
@NonNull private final Model mModel;
@@ -1389,6 +1394,47 @@ public abstract class ColorSpace {
}
/**
+ * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace}
+ * value.
+ *
+ * <p>This function maps from a dataspace to a {@link Named} ColorSpace.
+ * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created,
+ * {@code null} will return.</p>
+ *
+ * @param dataSpace The dataspace value
+ * @return the ColorSpace object or {@code null} if no matching colorspace can be found.
+ */
+ @SuppressLint("MethodNameUnits")
+ @Nullable
+ public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+ int index = sDataToColorSpaces.get(dataSpace, -1);
+ if (index != -1) {
+ return ColorSpace.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace}
+ * object.
+ *
+ * <p>If this {@link ColorSpace} object has no matching {@code dataSpace} value,
+ * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.</p>
+ *
+ * @return the dataspace value.
+ */
+ @SuppressLint("MethodNameUnits")
+ public @NamedDataSpace int getDataSpace() {
+ int index = sDataToColorSpaces.indexOfValue(getId());
+ if (index != -1) {
+ return sDataToColorSpaces.keyAt(index);
+ } else {
+ return DataSpace.DATASPACE_UNKNOWN;
+ }
+ }
+
+ /**
* <p>Returns an instance of {@link ColorSpace} identified by the specified
* name. The list of names provided in the {@link Named} enum gives access
* to a variety of common RGB color spaces.</p>
@@ -1445,6 +1491,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal());
sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
"sRGB IEC61966-2.1 (Linear)",
SRGB_PRIMARIES,
@@ -1453,6 +1500,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.LINEAR_SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal());
sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
@@ -1464,6 +1512,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.EXTENDED_SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal());
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
"scRGB IEC 61966-2-2:2003",
SRGB_PRIMARIES,
@@ -1472,6 +1521,8 @@ public abstract class ColorSpace {
-0.5f, 7.499f,
Named.LINEAR_EXTENDED_SRGB.ordinal()
);
+ sDataToColorSpaces.put(
+ DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.709-5",
new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
@@ -1480,6 +1531,7 @@ public abstract class ColorSpace {
new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
Named.BT709.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.2020-1",
new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
@@ -1488,6 +1540,7 @@ public abstract class ColorSpace {
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
Named.BT2020.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
"SMPTE RP 431-2-2007 DCI (P3)",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1496,6 +1549,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.DCI_P3.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1504,6 +1558,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal());
sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
"NTSC (1953)",
NTSC_1953_PRIMARIES,
@@ -1528,6 +1583,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.ADOBE_RGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal());
sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
"ROMM RGB ISO 22028-2:2013",
new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 6ded16371e8a..9f5bfd40e7e3 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -34,6 +34,7 @@ license {
android_app {
name: "CompanionDeviceManager",
defaults: ["platform_app_defaults"],
+ certificate: "platform",
srcs: ["src/**/*.java"],
static_libs: [
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 06f2d9d0f0c8..8b5d214f7a10 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -31,6 +31,7 @@
<uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<application
android:allowClearUserData="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index b51d3103caec..a6a8fcf9af62 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -37,6 +37,7 @@ import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
@@ -71,6 +72,9 @@ public class CompanionDeviceActivity extends AppCompatActivity {
private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+ // Activity result: Internal Error.
+ private static final int RESULT_INTERNAL_ERROR = 2;
+
// AssociationRequestsProcessor -> UI
private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
private static final String EXTRA_ASSOCIATION = "association";
@@ -191,6 +195,20 @@ public class CompanionDeviceActivity extends AppCompatActivity {
private void initUI() {
if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
+ final String packageName = mRequest.getPackageName();
+ final int userId = mRequest.getUserId();
+ final CharSequence appLabel;
+
+ try {
+ appLabel = getApplicationLabel(this, packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
+
+ CompanionDeviceDiscoveryService.stop(this);
+ setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+ return;
+ }
+
setContentView(R.layout.activity_confirmation);
mTitle = findViewById(R.id.title);
@@ -203,8 +221,6 @@ public class CompanionDeviceActivity extends AppCompatActivity {
mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
- final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
-
if (mRequest.isSelfManaged()) {
initUiForSelfManagedAssociation(appLabel);
} else if (mRequest.isSingleDevice()) {
@@ -257,7 +273,7 @@ public class CompanionDeviceActivity extends AppCompatActivity {
if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
// Don't need to notify the app, CdmService has already done that. Just finish.
- setResultAndFinish(association);
+ setResultAndFinish(association, RESULT_OK);
}
private void cancel(boolean discoveryTimeout) {
@@ -284,10 +300,10 @@ public class CompanionDeviceActivity extends AppCompatActivity {
}
// ... then set result and finish ("sending" onActivityResult()).
- setResultAndFinish(null);
+ setResultAndFinish(null, RESULT_CANCELED);
}
- private void setResultAndFinish(@Nullable AssociationInfo association) {
+ private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association);
final Intent data = new Intent();
@@ -297,7 +313,7 @@ public class CompanionDeviceActivity extends AppCompatActivity {
data.putExtra(CompanionDeviceManager.EXTRA_DEVICE, mSelectedDevice.getDevice());
}
}
- setResult(association != null ? RESULT_OK : RESULT_CANCELED, data);
+ setResult(resultCode, data);
finish();
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index eab421e48446..e3e563d56e8a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -50,14 +50,13 @@ class Utils {
}
static @NonNull CharSequence getApplicationLabel(
- @NonNull Context context, @NonNull String packageName) {
+ @NonNull Context context, @NonNull String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
final PackageManager packageManager = context.getPackageManager();
- final ApplicationInfo appInfo;
- try {
- appInfo = packageManager.getApplicationInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
- }
+
+ final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
+
return packageManager.getApplicationLabel(appInfo);
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index 9175809d9c7c..f681ba1c3853 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -28,6 +28,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
+import android.text.TextUtils;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -500,7 +501,7 @@ public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Ent
&& roaming == e.roaming && defaultNetwork == e.defaultNetwork
&& rxBytes == e.rxBytes && rxPackets == e.rxPackets
&& txBytes == e.txBytes && txPackets == e.txPackets
- && operations == e.operations && iface.equals(e.iface);
+ && operations == e.operations && TextUtils.equals(iface, e.iface);
}
return false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
new file mode 100644
index 000000000000..8aee576c3d04
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
@@ -0,0 +1,54 @@
+/*
+ * 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.settingslib.devicestate;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.Settings;
+
+/**
+ * Implementation of {@link SecureSettings} that uses Android's {@link Settings.Secure}
+ * implementation.
+ */
+class AndroidSecureSettings implements SecureSettings {
+
+ private final ContentResolver mContentResolver;
+
+ AndroidSecureSettings(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ @Override
+ public void putStringForUser(String name, String value, int userHandle) {
+ Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle);
+ }
+
+ @Override
+ public String getStringForUser(String name, int userHandle) {
+ return Settings.Secure.getStringForUser(mContentResolver, name, userHandle);
+ }
+
+ @Override
+ public void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver observer, int userHandle) {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(name),
+ notifyForDescendants,
+ observer,
+ userHandle);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index afd3626ab889..961fab32fc2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -24,6 +24,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -47,28 +48,33 @@ public final class DeviceStateRotationLockSettingsManager {
private static DeviceStateRotationLockSettingsManager sSingleton;
- private final ContentResolver mContentResolver;
private final String[] mDeviceStateRotationLockDefaults;
- private final Handler mMainHandler = Handler.getMain();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+ private final SecureSettings mSecureSettings;
private SparseIntArray mDeviceStateRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
+ private String mLastSettingValue;
- private DeviceStateRotationLockSettingsManager(Context context) {
- mContentResolver = context.getContentResolver();
+ @VisibleForTesting
+ DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
+ this.mSecureSettings = secureSettings;
mDeviceStateRotationLockDefaults =
context.getResources()
.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
loadDefaults();
initializeInMemoryMap();
- listenForSettingsChange(context);
+ listenForSettingsChange();
}
/** Returns a singleton instance of this class */
public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
if (sSingleton == null) {
+ Context applicationContext = context.getApplicationContext();
+ ContentResolver contentResolver = applicationContext.getContentResolver();
+ SecureSettings secureSettings = new AndroidSecureSettings(contentResolver);
sSingleton =
- new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
+ new DeviceStateRotationLockSettingsManager(applicationContext, secureSettings);
}
return sSingleton;
}
@@ -81,11 +87,11 @@ public final class DeviceStateRotationLockSettingsManager {
> 0;
}
- private void listenForSettingsChange(Context context) {
- context.getContentResolver()
+ private void listenForSettingsChange() {
+ mSecureSettings
.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
- /* notifyForDescendents= */ false, //NOTYPO
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* notifyForDescendants= */ false,
new ContentObserver(mMainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -182,8 +188,7 @@ public final class DeviceStateRotationLockSettingsManager {
private void initializeInMemoryMap() {
String serializedSetting =
- Settings.Secure.getStringForUser(
- mContentResolver,
+ mSecureSettings.getStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
UserHandle.USER_CURRENT);
if (TextUtils.isEmpty(serializedSetting)) {
@@ -222,11 +227,7 @@ public final class DeviceStateRotationLockSettingsManager {
private void persistSettings() {
if (mDeviceStateRotationLockSettings.size() == 0) {
- Settings.Secure.putStringForUser(
- mContentResolver,
- Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- /* value= */ "",
- UserHandle.USER_CURRENT);
+ persistSettingIfChanged(/* newSettingValue= */ "");
return;
}
@@ -243,10 +244,17 @@ public final class DeviceStateRotationLockSettingsManager {
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.valueAt(i));
}
- Settings.Secure.putStringForUser(
- mContentResolver,
+ persistSettingIfChanged(stringBuilder.toString());
+ }
+
+ private void persistSettingIfChanged(String newSettingValue) {
+ if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
+ return;
+ }
+ mLastSettingValue = newSettingValue;
+ mSecureSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
- stringBuilder.toString(),
+ /* value= */ newSettingValue,
UserHandle.USER_CURRENT);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
new file mode 100644
index 000000000000..10528739b2b0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
@@ -0,0 +1,30 @@
+/*
+ * 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.settingslib.devicestate;
+
+import android.database.ContentObserver;
+
+/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
+interface SecureSettings {
+
+ void putStringForUser(String name, String value, int userHandle);
+
+ String getStringForUser(String name, int userHandle);
+
+ void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver settingsObserver, int userHandle);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 93be66ad4882..1e1dfae9f7ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -81,6 +81,7 @@ public class AvatarPickerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setTheme(R.style.SudThemeGlifV3_DayNight);
ThemeHelper.trySetDynamicColor(this);
setContentView(R.layout.avatar_picker);
setUpButtons();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
new file mode 100644
index 000000000000..1a45384bc768
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.settingslib.devicestate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateRotationLockSettingsManagerTest {
+
+ @Mock private Context mMockContext;
+ @Mock private Resources mMockResources;
+
+ private DeviceStateRotationLockSettingsManager mManager;
+ private int mNumSettingsChanges = 0;
+ private final ContentObserver mContentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mNumSettingsChanges++;
+ }
+ };
+ private final FakeSecureSettings mFakeSecureSettings = new FakeSecureSettings();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getTargetContext();
+ when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
+ mFakeSecureSettings.registerContentObserver(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* notifyForDescendents= */ false, //NOTYPO
+ mContentObserver,
+ UserHandle.USER_CURRENT);
+ mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings);
+ }
+
+ @Test
+ public void initialization_settingsAreChangedOnce() {
+ assertThat(mNumSettingsChanges).isEqualTo(1);
+ }
+
+ @Test
+ public void updateSetting_multipleTimes_sameValue_settingsAreChangedOnlyOnce() {
+ mNumSettingsChanges = 0;
+
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+ assertThat(mNumSettingsChanges).isEqualTo(1);
+ }
+
+ @Test
+ public void updateSetting_multipleTimes_differentValues_settingsAreChangedMultipleTimes() {
+ mNumSettingsChanges = 0;
+
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ false);
+ mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+ assertThat(mNumSettingsChanges).isEqualTo(3);
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
new file mode 100644
index 000000000000..91baa68a1c49
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
@@ -0,0 +1,60 @@
+/*
+ * 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.settingslib.devicestate;
+
+import android.database.ContentObserver;
+import android.util.Pair;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Fake implementation of {@link SecureSettings} that stores everything in memory. */
+class FakeSecureSettings implements SecureSettings {
+
+ private final Map<SettingsKey, String> mValues = new HashMap<>();
+ private final Multimap<SettingsKey, ContentObserver> mContentObservers = HashMultimap.create();
+
+ @Override
+ public void putStringForUser(String name, String value, int userHandle) {
+ SettingsKey settingsKey = new SettingsKey(userHandle, name);
+ mValues.put(settingsKey, value);
+ for (ContentObserver observer : mContentObservers.get(settingsKey)) {
+ observer.onChange(/* selfChange= */ false);
+ }
+ }
+
+ @Override
+ public String getStringForUser(String name, int userHandle) {
+ return mValues.getOrDefault(new SettingsKey(userHandle, name), "");
+ }
+
+ @Override
+ public void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver settingsObserver, int userHandle) {
+ mContentObservers.put(new SettingsKey(userHandle, name), settingsObserver);
+ }
+
+ private static class SettingsKey extends Pair<Integer, String> {
+
+ SettingsKey(Integer userHandle, String settingName) {
+ super(userHandle, settingName);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
new file mode 100644
index 000000000000..98f41234feb1
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
@@ -0,0 +1,3 @@
+# Default reviewers for this and subdirectories.
+alexflo@google.com
+chrisgollner@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c3f6a5d18b74..0da60f0b3d66 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -298,6 +298,9 @@ class ActivityLaunchAnimator(
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
* during the animation. For safety, this method will return null when it is not.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be
+ * properly animated.
*/
@JvmStatic
fun fromView(view: View, cujType: Int? = null): Controller? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a3c5649e3fec..50178f470fd8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -79,6 +79,9 @@ class DialogLaunchAnimator @JvmOverloads constructor(
* If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
* animated when the dialog bounds change.
*
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+ * animated.
+ *
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
*/
@@ -153,6 +156,9 @@ class DialogLaunchAnimator @JvmOverloads constructor(
* activity started, when the dialog to app animation is done (or when it is cancelled). If this
* method returns null, then the dialog won't be dismissed.
*
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+ * animated.
+ *
* @param view any view inside the dialog to animate.
*/
@JvmOverloads
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
new file mode 100644
index 000000000000..0544b871fa06
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="18dp"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="18dp"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index d057f5f32dc4..31a8c3bc2397 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -19,13 +19,17 @@
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
- <shape android:shape="oval">
+ <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+ <!-- properly into an app/dialog. -->
+ <shape android:shape="rectangle">
<solid android:color="@android:color/white"/>
+ <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
</shape>
</item>
<item>
- <shape android:shape="oval">
+ <shape android:shape="rectangle">
<solid android:color="?attr/offStateColor"/>
+ <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 944061cc8e70..021a85f6a244 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -19,13 +19,17 @@
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
- <shape android:shape="oval">
+ <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+ <!-- properly into an app/dialog. -->
+ <shape android:shape="rectangle">
<solid android:color="@android:color/white"/>
+ <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
</shape>
</item>
<item>
- <shape android:shape="oval">
+ <shape android:shape="rectangle">
<solid android:color="?android:attr/colorAccent"/>
+ <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
index a3e289a42d05..e06bfdc500da 100644
--- a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -22,10 +22,6 @@
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollIndicators="top|bottom"
android:fillViewport="true"
- android:paddingTop="@dimen/dialog_button_bar_top_padding"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding"
- android:paddingBottom="@dimen/dialog_bottom_padding"
style="?android:attr/buttonBarStyle">
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index f280cbd16a0f..ca8fadd9c7da 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -83,9 +83,15 @@
android:layout_height="wrap_content" />
</FrameLayout>
- <include
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- layout="@layout/alert_dialog_button_bar_systemui" />
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding">
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_button_bar_systemui" />
+ </FrameLayout>
</com.android.internal.widget.AlertDialogLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 330f515a29fc..8e83b4abe0dc 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -34,34 +34,5 @@
app:layout_constraintBottom_toBottomOf="parent"
/>
- <com.android.systemui.dreams.DreamOverlayStatusBarView
- android:id="@+id/dream_overlay_status_bar"
- android:layout_width="match_parent"
- android:layout_height="@dimen/dream_overlay_status_bar_height"
- android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
- android:paddingStart="@dimen/dream_overlay_status_bar_margin"
- app:layout_constraintTop_toTopOf="parent">
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/dream_overlay_system_status"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/dream_overlay_wifi_status"
- android:layout_width="@dimen/status_bar_wifi_signal_size"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
- android:visibility="gone"
- app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
-
- <com.android.systemui.battery.BatteryMeterView
- android:id="@+id/dream_overlay_battery"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- app:layout_constraintEnd_toEndOf="parent" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
- </com.android.systemui.dreams.DreamOverlayStatusBarView>
+ <include layout="@layout/dream_overlay_status_bar_view" />
</com.android.systemui.dreams.DreamOverlayContainerView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
new file mode 100644
index 000000000000..813787e8f9d0
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.systemui.dreams.DreamOverlayStatusBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/dream_overlay_status_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dream_overlay_status_bar_height"
+ android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
+ android:paddingStart="@dimen/dream_overlay_status_bar_margin"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.android.systemui.dreams.DreamOverlayDotImageView
+ android:id="@+id/dream_overlay_notification_indicator"
+ android:layout_width="@dimen/dream_overlay_notification_indicator_size"
+ android:layout_height="@dimen/dream_overlay_notification_indicator_size"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_notification_indicator"
+ app:dotColor="@android:color/white"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <LinearLayout
+ android:id="@+id/dream_overlay_system_status"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/dream_overlay_assistant_guest_mode_enabled"
+ android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/ic_account_circle"
+ android:tint="@android:color/white"
+ android:visibility="gone"
+ android:contentDescription=
+ "@string/dream_overlay_status_bar_assistant_guest_mode_enabled" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/dream_overlay_alarm_set"
+ android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/ic_alarm"
+ android:tint="@android:color/white"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_alarm_set" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/dream_overlay_priority_mode"
+ android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/ic_remove_circle"
+ android:tint="@android:color/white"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_priority_mode" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/dream_overlay_wifi_status"
+ android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/ic_signal_wifi_off"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
+
+ <com.android.systemui.dreams.DreamOverlayDotImageView
+ android:id="@+id/dream_overlay_camera_mic_off"
+ android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size"
+ android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off"
+ app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" />
+
+ </LinearLayout>
+</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 2992859b1ce7..c5e005c556b2 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -199,5 +199,9 @@
</declare-styleable>
<attr name="overlayButtonTextColor" format="color" />
+
+ <declare-styleable name="DreamOverlayDotImageView">
+ <attr name="dotColor" format="color" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f4e7cf3fcf40..dc7470081da2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -225,4 +225,6 @@
<color name="settingslib_track_off_color">@color/settingslib_track_off</color>
<color name="connected_network_primary_color">#191C18</color>
<color name="connected_network_secondary_color">#41493D</color>
+
+ <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 370413400766..fcf60bf43c62 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1332,12 +1332,16 @@
<dimen name="fgs_manager_min_width_minor">100%</dimen>
<!-- Dream overlay related dimensions -->
- <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+ <dimen name="dream_overlay_status_bar_height">60dp</dimen>
<dimen name="dream_overlay_status_bar_margin">40dp</dimen>
<dimen name="dream_overlay_status_icon_margin">8dp</dimen>
+ <dimen name="dream_overlay_status_bar_icon_size">
+ @*android:dimen/status_bar_system_icon_size</dimen>
<!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
shade. -->
<dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+ <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
+ <dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 23b252906056..df16b0d45228 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2410,4 +2410,17 @@
<!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] -->
<string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string>
+
+ <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string>
+ <!-- Content description for the priority mode icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_priority_mode">Priority mode</string>
+ <!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_alarm_set">Alarm set</string>
+ <!-- Content description for the assistant guest mode enabled icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_assistant_guest_mode_enabled">Assistant guest mode enabled</string>
+ <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
+ <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_notification_indicator">There are notifications</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9d65c3895827..f2eaa75496e8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -381,12 +381,19 @@
<item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
<item name="android:colorBackground">?androidprv:attr/colorSurface</item>
<item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+ <item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
+ <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
</style>
<style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
<item name="android:layout">@layout/alert_dialog_systemui</item>
</style>
+ <style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog">
+ <item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item>
+ <item name="android:paddingBottom">@dimen/dialog_bottom_padding</item>
+ </style>
+
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
<style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
@@ -962,6 +969,11 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
+ <style name="Widget.Dialog.Button.Large">
+ <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
+ <item name="android:minHeight">56dp</item>
+ </style>
+
<style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
<item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
</style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 08b4d3f68a87..2b1c47f60070 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -103,8 +103,8 @@ public class QuickStepContract {
// enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
// stack.
public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
- // The global actions dialog is showing
- public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15;
+ // A SysUI dialog is showing.
+ public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
// The one-handed mode is active
public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
// Allow system gesture no matter the system bar(s) is visible or not
@@ -140,7 +140,7 @@ public class QuickStepContract {
SYSUI_STATE_TRACING_ENABLED,
SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
SYSUI_STATE_BUBBLES_EXPANDED,
- SYSUI_STATE_GLOBAL_ACTIONS_SHOWING,
+ SYSUI_STATE_DIALOG_SHOWING,
SYSUI_STATE_ONE_HANDED_ACTIVE,
SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
SYSUI_STATE_IME_SHOWING,
@@ -166,7 +166,7 @@ public class QuickStepContract {
str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
? "keygrd_occluded" : "");
str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
- str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : "");
+ str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : "");
str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
@@ -256,7 +256,7 @@ public class QuickStepContract {
public static boolean isBackGestureDisabled(int sysuiStateFlags) {
// Always allow when the bouncer/global actions is showing (even on top of the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) {
+ || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0) {
return false;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b32c2b639f16..84b6ace17ab9 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -38,6 +38,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -107,6 +108,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -372,6 +374,8 @@ public class Dependency {
@Inject Lazy<AmbientState> mAmbientStateLazy;
@Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
@Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
+ @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
+ @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
@Inject
public Dependency() {
@@ -592,6 +596,8 @@ public class Dependency {
mProviders.put(AmbientState.class, mAmbientStateLazy::get);
mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
+ mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
+ mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
new file mode 100644
index 000000000000..02a8b39a106a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
@@ -0,0 +1,128 @@
+/*
+ * 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.dreams;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
+
+/**
+ * An {@link AlphaOptimizedImageView} that is responsible for rendering a dot. Used by
+ * {@link DreamOverlayStatusBarView}.
+ */
+public class DreamOverlayDotImageView extends AlphaOptimizedImageView {
+ private final @ColorInt int mDotColor;
+
+ public DreamOverlayDotImageView(Context context) {
+ this(context, null);
+ }
+
+ public DreamOverlayDotImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+ R.styleable.DreamOverlayDotImageView, 0, 0);
+
+ try {
+ mDotColor = a.getColor(R.styleable.DreamOverlayDotImageView_dotColor, Color.WHITE);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setImageDrawable(new DotDrawable(mDotColor));
+ }
+
+ private static class DotDrawable extends Drawable {
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Bitmap mDotBitmap;
+ private final Rect mBounds = new Rect();
+ private final @ColorInt int mDotColor;
+
+ DotDrawable(@ColorInt int color) {
+ mDotColor = color;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ if (mBounds.isEmpty()) {
+ return;
+ }
+
+ if (mDotBitmap == null) {
+ mDotBitmap = createBitmap(mBounds.width(), mBounds.height());
+ }
+
+ canvas.drawBitmap(mDotBitmap, null, mBounds, mPaint);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ // Make sure to regenerate the dot bitmap when the bounds change.
+ mDotBitmap = null;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ private Bitmap createBitmap(int width, int height) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(mDotColor);
+ canvas.drawCircle(width / 2.f, height / 2.f, Math.min(width, height) / 2.f, paint);
+ return bitmap;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 9847ef633bc1..2d969206b468 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -25,17 +25,13 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
/**
* {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
- * dream. The status bar includes status icons such as battery and wifi.
+ * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi".
*/
-public class DreamOverlayStatusBarView extends ConstraintLayout implements
- BatteryStateChangeCallback {
+public class DreamOverlayStatusBarView extends ConstraintLayout {
- private BatteryMeterView mBatteryView;
private ImageView mWifiStatusView;
public DreamOverlayStatusBarView(Context context) {
@@ -59,20 +55,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout implements
protected void onFinishInflate() {
super.onFinishInflate();
- mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
- "R.id.dream_overlay_battery must not be null");
mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
"R.id.dream_overlay_wifi_status must not be null");
-
- mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
- }
-
- /**
- * Whether to show the battery percent text next to the battery status icons.
- * @param show True if the battery percent text should be shown.
- */
- void showBatteryPercentText(boolean show) {
- mBatteryView.setForceShowPercent(show);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 5674b9f3f9fd..ed82ab0e308f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -17,24 +17,19 @@
package com.android.systemui.dreams;
import android.annotation.IntDef;
-import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* View controller for {@link DreamOverlayStatusBarView}.
@@ -52,21 +47,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
private static final int WIFI_STATUS_UNAVAILABLE = 1;
private static final int WIFI_STATUS_AVAILABLE = 2;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
- BATTERY_STATUS_UNKNOWN,
- BATTERY_STATUS_NOT_CHARGING,
- BATTERY_STATUS_CHARGING
- })
- private @interface BatteryStatus {}
- private static final int BATTERY_STATUS_UNKNOWN = 0;
- private static final int BATTERY_STATUS_NOT_CHARGING = 1;
- private static final int BATTERY_STATUS_CHARGING = 2;
-
- private final BatteryController mBatteryController;
- private final BatteryMeterViewController mBatteryMeterViewController;
private final ConnectivityManager mConnectivityManager;
- private final boolean mShowPercentAvailable;
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities()
@@ -91,43 +72,18 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
}
};
- private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
- new BatteryController.BatteryStateChangeCallback() {
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
- }
- };
-
private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
- private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
@Inject
public DreamOverlayStatusBarViewController(
- Context context,
DreamOverlayStatusBarView view,
- BatteryController batteryController,
- @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
- BatteryMeterViewController batteryMeterViewController,
ConnectivityManager connectivityManager) {
super(view);
- mBatteryController = batteryController;
- mBatteryMeterViewController = batteryMeterViewController;
mConnectivityManager = connectivityManager;
-
- mShowPercentAvailable = context.getResources().getBoolean(
- com.android.internal.R.bool.config_battery_percentage_setting_available);
- }
-
- @Override
- protected void onInit() {
- super.onInit();
- mBatteryMeterViewController.init();
}
@Override
protected void onViewAttached() {
- mBatteryController.addCallback(mBatteryStateChangeCallback);
mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
NetworkCapabilities capabilities =
@@ -140,7 +96,6 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
@Override
protected void onViewDetached() {
- mBatteryController.removeCallback(mBatteryStateChangeCallback);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
@@ -155,18 +110,4 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
}
}
-
- /**
- * The battery level has changed. Update the battery status icon as appropriate.
- * @param charging Whether the battery is currently charging.
- */
- private void onBatteryLevelChanged(boolean charging) {
- final int newBatteryStatus =
- charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
- if (mBatteryStatus != newBatteryStatus) {
- mBatteryStatus = newBatteryStatus;
- mView.showBatteryPercentText(
- mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4eb5cb97607a..839a05e6e78f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,9 +16,7 @@
package com.android.systemui.dreams.dagger;
-import android.content.ContentResolver;
import android.content.res.Resources;
-import android.os.Handler;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -28,15 +26,9 @@ import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
import javax.inject.Named;
@@ -47,9 +39,6 @@ import dagger.Provides;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
public abstract class DreamOverlayModule {
- private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
- public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
- "dream_overlay_battery_controller";
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -86,37 +75,6 @@ public abstract class DreamOverlayModule {
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
- @Named(DREAM_OVERLAY_BATTERY_VIEW)
- static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
- return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
- "R.id.battery must not be null");
- }
-
- /** */
- @Provides
- @DreamOverlayComponent.DreamOverlayScope
- @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
- static BatteryMeterViewController providesBatteryMeterViewController(
- @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
- ConfigurationController configurationController,
- TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
- @Main Handler mainHandler,
- ContentResolver contentResolver,
- BatteryController batteryController) {
- return new BatteryMeterViewController(
- batteryMeterView,
- configurationController,
- tunerService,
- broadcastDispatcher,
- mainHandler,
- contentResolver,
- batteryController);
- }
-
- /** */
- @Provides
- @DreamOverlayComponent.DreamOverlayScope
@Named(MAX_BURN_IN_OFFSET)
static int providesMaxBurnInOffset(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 84fa6a6772fe..e3886cd80a42 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -27,7 +27,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -117,7 +116,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
@@ -125,7 +123,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -200,7 +197,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
- private final SysUiState mSysUiState;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -241,7 +237,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
private final Optional<StatusBar> mStatusBarOptional;
- private final SystemUIDialogManager mDialogManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -347,13 +342,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
@Background Executor backgroundExecutor,
UiEventLogger uiEventLogger,
RingerModeTracker ringerModeTracker,
- SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
Optional<StatusBar> statusBarOptional,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- DialogLaunchAnimator dialogLaunchAnimator,
- SystemUIDialogManager dialogManager) {
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -379,13 +372,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mIWindowManager = iWindowManager;
mBackgroundExecutor = backgroundExecutor;
mRingerModeTracker = ringerModeTracker;
- mSysUiState = sysUiState;
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDialogLaunchAnimator = dialogLaunchAnimator;
- mDialogManager = dialogManager;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -682,11 +673,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
ActionsDialogLite dialog = new ActionsDialogLite(mContext,
com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
- mAdapter, mOverflowAdapter, mSysuiColorExtractor,
- mStatusBarService, mNotificationShadeWindowController,
- mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
- mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils,
- mDialogManager);
+ mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
+ mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
+ mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor,
+ mLockPatternUtils);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -2165,7 +2155,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private boolean mKeyguardShowing;
protected float mScrimAlpha;
protected final NotificationShadeWindowController mNotificationShadeWindowController;
- protected final SysUiState mSysUiState;
private ListPopupWindow mOverflowPopup;
private Dialog mPowerOptionsDialog;
protected final Runnable mOnRefreshCallback;
@@ -2226,15 +2215,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
MyOverflowAdapter overflowAdapter,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing,
+ Runnable onRefreshCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- Optional<StatusBar> statusBarOptional,
- KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
- SystemUIDialogManager systemUiDialogManager) {
+ Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils) {
// We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
// dismiss this dialog when the device is locked.
- super(context, themeRes, false /* dismissOnDeviceLock */,
- systemUiDialogManager);
+ super(context, themeRes, false /* dismissOnDeviceLock */);
mContext = context;
mAdapter = adapter;
mOverflowAdapter = overflowAdapter;
@@ -2242,7 +2229,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mColorExtractor = sysuiColorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
- mSysUiState = sysuiState;
mOnRefreshCallback = onRefreshCallback;
mKeyguardShowing = keyguardShowing;
mUiEventLogger = uiEventLogger;
@@ -2463,8 +2449,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
public void show() {
super.show();
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
- .commitUpdate(mContext.getDisplayId());
// By default this dialog windowAnimationStyle is null, and therefore windowAnimations
// should be equal to 0 which means we need to animate the dialog in-window. If it's not
@@ -2563,9 +2547,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
dismissPowerOptions();
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
- .commitUpdate(mContext.getDisplayId());
-
super.dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7bb5454112c4..04a324b87382 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -56,7 +56,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
/**
* Base dialog for media output UI
@@ -99,9 +98,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
};
- public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController,
- SystemUIDialogManager dialogManager) {
- super(context, R.style.Theme_SystemUI_Dialog_Media, dialogManager);
+ public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
+ super(context, R.style.Theme_SystemUI_Dialog_Media);
// Save the context that is wrapped with our theme.
mContext = getContext();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 7bc0f5202c91..e929b5e21053 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -66,7 +66,6 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import java.util.ArrayList;
import java.util.Collection;
@@ -91,7 +90,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
private final ShadeController mShadeController;
private final ActivityStarter mActivityStarter;
private final DialogLaunchAnimator mDialogLaunchAnimator;
- private final SystemUIDialogManager mDialogManager;
private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
private final boolean mAboveStatusbar;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
@@ -119,7 +117,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
lbm, ShadeController shadeController, ActivityStarter starter,
CommonNotifCollection notifCollection, UiEventLogger uiEventLogger,
- DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) {
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -135,7 +133,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
mDialogLaunchAnimator = dialogLaunchAnimator;
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
- mDialogManager = dialogManager;
mColorActiveItem = Utils.getColorStateListDefaultColor(mContext,
R.color.media_dialog_active_item_main_content);
mColorInactiveItem = Utils.getColorStateListDefaultColor(mContext,
@@ -610,10 +607,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
// We show the output group dialog from the output dialog.
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
- mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
- mDialogManager);
+ mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
- controller, mDialogManager);
+ controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 4e9da55ffbcb..7696a1f63c01 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -29,7 +29,6 @@ import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
/**
* Dialog for media output transferring.
@@ -39,9 +38,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
final UiEventLogger mUiEventLogger;
MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
- mediaOutputController, UiEventLogger uiEventLogger,
- SystemUIDialogManager dialogManager) {
- super(context, mediaOutputController, dialogManager);
+ mediaOutputController, UiEventLogger uiEventLogger) {
+ super(context, mediaOutputController);
mUiEventLogger = uiEventLogger;
mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index e1e7fa3ebbe0..9e252ea1eddc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -25,7 +25,6 @@ import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.phone.ShadeController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
import javax.inject.Inject
/**
@@ -39,8 +38,7 @@ class MediaOutputDialogFactory @Inject constructor(
private val starter: ActivityStarter,
private val notifCollection: CommonNotifCollection,
private val uiEventLogger: UiEventLogger,
- private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val dialogManager: SystemUIDialogManager
+ private val dialogLaunchAnimator: DialogLaunchAnimator
) {
companion object {
var mediaOutputDialog: MediaOutputDialog? = null
@@ -53,9 +51,8 @@ class MediaOutputDialogFactory @Inject constructor(
val controller = MediaOutputController(context, packageName, aboveStatusBar,
mediaSessionManager, lbm, shadeController, starter, notifCollection,
- uiEventLogger, dialogLaunchAnimator, dialogManager)
- val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger,
- dialogManager)
+ uiEventLogger, dialogLaunchAnimator)
+ val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
mediaOutputDialog = dialog
// Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index 9f752b92a85e..f1c66016a49a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -25,7 +25,6 @@ import android.view.WindowManager;
import androidx.core.graphics.drawable.IconCompat;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
/**
* Dialog for media output group.
@@ -34,8 +33,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager;
public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
- mediaOutputController, SystemUIDialogManager dialogManager) {
- super(context, mediaOutputController, dialogManager);
+ mediaOutputController) {
+ super(context, mediaOutputController);
mMediaOutputController.resetGroupMediaDevices();
mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index e088f548a356..5d6bbae1f14a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -202,11 +202,12 @@ public class CastTile extends QSTileImpl<BooleanState> {
mActivityStarter
.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
controller);
- });
+ }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */);
holder.init(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
SystemUIDialog.registerDismissListener(dialog);
SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing());
+ SystemUIDialog.setDialogSize(dialog);
mUiHandler.post(() -> {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 97225284f208..79d646cdbd13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -42,7 +42,10 @@ import androidx.annotation.Nullable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;
import java.util.List;
@@ -64,7 +67,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final Context mContext;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
- @Nullable private final SystemUIDialogManager mDialogManager;
+ private final SystemUIDialogManager mDialogManager;
+ private final SysUiState mSysUiState;
private int mLastWidth = Integer.MIN_VALUE;
private int mLastHeight = Integer.MIN_VALUE;
@@ -77,24 +81,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
this(context, R.style.Theme_SystemUI_Dialog);
}
- public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) {
- this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager);
- }
-
public SystemUIDialog(Context context, int theme) {
this(context, theme, true /* dismissOnDeviceLock */);
}
- public SystemUIDialog(Context context, int theme, SystemUIDialogManager dialogManager) {
- this(context, theme, true /* dismissOnDeviceLock */, dialogManager);
- }
-
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
- this(context, theme, dismissOnDeviceLock, null);
- }
-
- public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
- @Nullable SystemUIDialogManager dialogManager) {
super(context, theme);
mContext = context;
@@ -104,7 +95,12 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
getWindow().setAttributes(attrs);
mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
- mDialogManager = dialogManager;
+
+ // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
+ // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
+ // the content and attach listeners.
+ mDialogManager = Dependency.get(SystemUIDialogManager.class);
+ mSysUiState = Dependency.get(SysUiState.class);
}
@Override
@@ -174,13 +170,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
mDismissReceiver.register();
}
- if (mDialogManager != null) {
- mDialogManager.setShowing(this, true);
- }
-
// Listen for configuration changes to resize this dialog window. This is mostly necessary
// for foldables that often go from large <=> small screen when folding/unfolding.
ViewRootImpl.addConfigCallback(this);
+ mDialogManager.setShowing(this, true);
+ mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true);
}
@Override
@@ -191,11 +185,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
mDismissReceiver.unregister();
}
- if (mDialogManager != null) {
- mDialogManager.setShowing(this, false);
- }
-
ViewRootImpl.removeConfigCallback(this);
+ mDialogManager.setShowing(this, false);
+ mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false);
}
public void setShowForAllUsers(boolean show) {
@@ -401,10 +393,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final Dialog mDialog;
private boolean mRegistered;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
DismissReceiver(Dialog dialog) {
mDialog = dialog;
+ // TODO(b/219008720): Remove those calls to Dependency.get.
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+ mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class);
}
void register() {
@@ -421,6 +416,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
@Override
public void onReceive(Context context, Intent intent) {
+ // These broadcast are usually received when locking the device, swiping up to home
+ // (which collapses the shade), etc. In those cases, we usually don't want to animate
+ // back into the view.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6fefce2be19b..b2a79b01fb74 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,7 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -97,7 +97,7 @@ public final class WMShell extends CoreStartable
implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = WMShell.class.getName();
private static final int INVALID_SYSUI_STATE_MASK =
- SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+ SYSUI_STATE_DIALOG_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
| SYSUI_STATE_BOUNCER_SHOWING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 40632a85d722..7a0db1fd975c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.After;
import org.junit.AfterClass;
@@ -112,6 +113,11 @@ public abstract class SysuiTestCase {
// KeyguardUpdateMonitor to be created (injected).
// TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
mDependency.injectMockDependency(SmartReplyController.class);
+
+ // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+ // is missing (constructing the actual one would throw).
+ // TODO(b/219008720): Remove this.
+ mDependency.injectMockDependency(SystemUIDialogManager.class);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 7f72dda441d2..658702929d7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -29,8 +29,6 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.statusbar.policy.BatteryController;
import org.junit.Before;
import org.junit.Test;
@@ -46,10 +44,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarView mView;
@Mock
- BatteryController mBatteryController;
- @Mock
- BatteryMeterViewController mBatteryMeterViewController;
- @Mock
ConnectivityManager mConnectivityManager;
@Mock
NetworkCapabilities mNetworkCapabilities;
@@ -61,22 +55,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mController = new DreamOverlayStatusBarViewController(
- mContext, mView, mBatteryController, mBatteryMeterViewController,
- mConnectivityManager);
- }
-
- @Test
- public void testOnInitInitializesControllers() {
- mController.onInit();
- verify(mBatteryMeterViewController).init();
- }
-
- @Test
- public void testOnViewAttachedAddsBatteryControllerCallback() {
- mController.onViewAttached();
- verify(mBatteryController)
- .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+ mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager);
}
@Test
@@ -113,13 +92,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
- public void testOnViewDetachedRemovesBatteryControllerCallback() {
- mController.onViewDetached();
- verify(mBatteryController)
- .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
- }
-
- @Test
public void testOnViewDetachedUnregistersNetworkCallback() {
mController.onViewDetached();
verify(mConnectivityManager)
@@ -127,26 +99,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
- public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
- final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
- mController.onViewAttached();
- verify(mBatteryController).addCallback(callbackCapture.capture());
- callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
- verify(mView).showBatteryPercentText(true);
- }
-
- @Test
- public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
- final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
- mController.onViewAttached();
- verify(mBatteryController).addCallback(callbackCapture.capture());
- callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
- verify(mView).showBatteryPercentText(false);
- }
-
- @Test
public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
// Make sure wifi starts out unavailable when onViewAttached is called.
when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 71fc8ee6cce8..953be7d6f002 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -19,7 +19,6 @@ package com.android.systemui.globalactions;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -57,13 +56,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -112,7 +109,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private UiEventLogger mUiEventLogger;
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
- @Mock private SysUiState mSysUiState;
@Mock private PackageManager mPackageManager;
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@@ -120,7 +116,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private StatusBar mStatusBar;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
- @Mock private SystemUIDialogManager mDialogManager;
private TestableLooper mTestableLooper;
@@ -161,19 +156,16 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mBackgroundExecutor,
mUiEventLogger,
mRingerModeTracker,
- mSysUiState,
mHandler,
mPackageManager,
Optional.of(mStatusBar),
mKeyguardUpdateMonitor,
- mDialogLaunchAnimator,
- mDialogManager);
+ mDialogLaunchAnimator);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
backdropColors.setMainColor(Color.BLACK);
when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c5c4d79e043d..2be30b39763e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -45,7 +45,6 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.Before;
import org.junit.Test;
@@ -68,7 +67,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
- private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
private MediaOutputController mMediaOutputController;
@@ -82,7 +80,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -175,7 +173,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
- super(context, mediaOutputController, mDialogManager);
+ super(context, mediaOutputController);
mAdapter = mMediaOutputBaseAdapter;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index bdc311725880..789822e262d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -55,7 +55,6 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.Before;
import org.junit.Test;
@@ -94,7 +93,6 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
- private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -117,7 +115,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -161,7 +159,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void start_withoutPackageName_verifyMediaControllerInit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.start(mCb);
@@ -182,7 +180,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.start(mCb);
@@ -453,7 +451,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index ada8d3592012..8a3ea562269d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -40,7 +40,6 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.After;
import org.junit.Before;
@@ -68,7 +67,6 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
- private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
private MediaOutputDialog mMediaOutputDialog;
private MediaOutputController mMediaOutputController;
@@ -78,10 +76,10 @@ public class MediaOutputDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false,
- mMediaOutputController, mUiEventLogger, mDialogManager);
+ mMediaOutputController, mUiEventLogger);
mMediaOutputDialog.show();
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -127,7 +125,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
- mMediaOutputController, mUiEventLogger, mDialogManager);
+ mMediaOutputController, mUiEventLogger);
testDialog.show();
testDialog.dismissDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index b114452facc3..e8cd6c88956d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -38,7 +38,6 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.After;
import org.junit.Before;
@@ -67,7 +66,6 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
- private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
private MediaOutputGroupDialog mMediaOutputGroupDialog;
private MediaOutputController mMediaOutputController;
@@ -77,10 +75,10 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
- mMediaOutputController, mDialogManager);
+ mMediaOutputController);
mMediaOutputGroupDialog.show();
when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 62da981bd9a7..0e9926590511 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1363,8 +1363,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* </p>
*
* @param displayId The logical display id
- * @param region the new magnified region, may be empty if
- * magnification is not enabled (e.g. scale is 1)
+ * @param region The magnification region.
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+ * it is the region of the screen currently active for magnification.
+ * the returned region will be empty if the magnification is not active
+ * (e.g. scale is 1. And the magnification is active if magnification
+ * gestures are enabled or if a service is running that can control
+ * magnification.
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+ * it is the region of screen projected on the magnification window.
+ * The region will be empty if magnification is not activated.
* @param config The magnification config. That has magnification mode, the new scale and the
* new screen-relative center position
*/
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index fe97a46e12f9..a95820966926 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -50,7 +50,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
-import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.wm.WindowManagerInternal;
@@ -374,9 +373,8 @@ public class FullScreenMagnificationController implements
.setScale(getScale())
.setCenterX(getCenterX())
.setCenterY(getCenterY()).build();
- mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId,
- mMagnificationRegion,
- config);
+ mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
+ mMagnificationRegion, config);
if (mUnregisterPending && !isMagnifying()) {
unregister(mDeleteAfterUnregister);
}
@@ -665,10 +663,10 @@ public class FullScreenMagnificationController implements
* FullScreenMagnificationController Constructor
*/
public FullScreenMagnificationController(@NonNull Context context,
- @NonNull AccessibilityManagerService ams, @NonNull Object lock,
+ @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider) {
- this(new ControllerContext(context, ams,
+ this(new ControllerContext(context, traceManager,
LocalServices.getService(WindowManagerInternal.class),
new Handler(context.getMainLooper()),
context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
@@ -1521,7 +1519,6 @@ public class FullScreenMagnificationController implements
@VisibleForTesting
public static class ControllerContext {
private final Context mContext;
- private final AccessibilityManagerService mAms;
private final AccessibilityTraceManager mTrace;
private final WindowManagerInternal mWindowManager;
private final Handler mHandler;
@@ -1531,13 +1528,12 @@ public class FullScreenMagnificationController implements
* Constructor for ControllerContext.
*/
public ControllerContext(@NonNull Context context,
- @NonNull AccessibilityManagerService ams,
+ @NonNull AccessibilityTraceManager traceManager,
@NonNull WindowManagerInternal windowManager,
@NonNull Handler handler,
long animationDuration) {
mContext = context;
- mAms = ams;
- mTrace = ams.getTraceManager();
+ mTrace = traceManager;
mWindowManager = windowManager;
mHandler = handler;
mAnimationDuration = animationDuration;
@@ -1552,14 +1548,6 @@ public class FullScreenMagnificationController implements
}
/**
- * @return AccessibilityManagerService
- */
- @NonNull
- public AccessibilityManagerService getAms() {
- return mAms;
- }
-
- /**
* @return AccessibilityTraceManager
*/
@NonNull
@@ -1632,5 +1620,17 @@ public class FullScreenMagnificationController implements
* hidden.
*/
void onImeWindowVisibilityChanged(boolean shown);
+
+ /**
+ * Called when the magnification spec changed.
+ *
+ * @param displayId The logical display id
+ * @param region The region of the screen currently active for magnification.
+ * The returned region will be empty if the magnification is not active.
+ * @param config The magnification config. That has magnification mode, the new scale and
+ * the new screen-relative center position
+ */
+ void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+ @NonNull MagnificationConfig config);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index c376bf873dc8..09e82c787c90 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -405,6 +405,12 @@ public class MagnificationController implements WindowMagnificationManager.Callb
mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
}
+ @Override
+ public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+ @NonNull MagnificationConfig config) {
+ mAms.notifyMagnificationChanged(displayId, region, config);
+ }
+
private void disableFullScreenMagnificationIfNeeded(int displayId) {
final FullScreenMagnificationController fullScreenMagnificationController =
getFullScreenMagnificationController();
@@ -590,7 +596,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
synchronized (mLock) {
if (mFullScreenMagnificationController == null) {
mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
- mAms, mLock, this, mScaleProvider);
+ mAms.getTraceManager(), mLock, this, mScaleProvider);
}
}
return mFullScreenMagnificationController;
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 89633373b152..7e3ede15aa04 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -22,20 +22,18 @@ import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
-import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
import java.util.ArrayDeque;
-import java.util.Deque;
+import java.util.HashSet;
import java.util.List;
import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -49,17 +47,19 @@ public class BackupTransportClient {
private final IBackupTransport mTransportBinder;
private final TransportStatusCallbackPool mCallbackPool;
+ private final TransportFutures mTransportFutures;
BackupTransportClient(IBackupTransport transportBinder) {
mTransportBinder = transportBinder;
mCallbackPool = new TransportStatusCallbackPool();
+ mTransportFutures = new TransportFutures();
}
/**
* See {@link IBackupTransport#name()}.
*/
public String name() throws RemoteException {
- AndroidFuture<String> resultFuture = new AndroidFuture<>();
+ AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.name(resultFuture);
return getFutureResult(resultFuture);
}
@@ -68,7 +68,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#configurationIntent()}
*/
public Intent configurationIntent() throws RemoteException {
- AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
mTransportBinder.configurationIntent(resultFuture);
return getFutureResult(resultFuture);
}
@@ -77,7 +77,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#currentDestinationString()}
*/
public String currentDestinationString() throws RemoteException {
- AndroidFuture<String> resultFuture = new AndroidFuture<>();
+ AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.currentDestinationString(resultFuture);
return getFutureResult(resultFuture);
}
@@ -86,7 +86,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#dataManagementIntent()}
*/
public Intent dataManagementIntent() throws RemoteException {
- AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
mTransportBinder.dataManagementIntent(resultFuture);
return getFutureResult(resultFuture);
}
@@ -96,7 +96,7 @@ public class BackupTransportClient {
*/
@Nullable
public CharSequence dataManagementIntentLabel() throws RemoteException {
- AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>();
+ AndroidFuture<CharSequence> resultFuture = mTransportFutures.newFuture();
mTransportBinder.dataManagementIntentLabel(resultFuture);
return getFutureResult(resultFuture);
}
@@ -105,7 +105,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#transportDirName()}
*/
public String transportDirName() throws RemoteException {
- AndroidFuture<String> resultFuture = new AndroidFuture<>();
+ AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.transportDirName(resultFuture);
return getFutureResult(resultFuture);
}
@@ -153,7 +153,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#requestBackupTime()}
*/
public long requestBackupTime() throws RemoteException {
- AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.requestBackupTime(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -177,7 +177,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#getAvailableRestoreSets()}
*/
public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
- AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>();
+ AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getAvailableRestoreSets(resultFuture);
List<RestoreSet> result = getFutureResult(resultFuture);
return result == null ? null : result.toArray(new RestoreSet[] {});
@@ -187,7 +187,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#getCurrentRestoreSet()}
*/
public long getCurrentRestoreSet() throws RemoteException {
- AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getCurrentRestoreSet(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -210,7 +210,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#nextRestorePackage()}
*/
public RestoreDescription nextRestorePackage() throws RemoteException {
- AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>();
+ AndroidFuture<RestoreDescription> resultFuture = mTransportFutures.newFuture();
mTransportBinder.nextRestorePackage(resultFuture);
return getFutureResult(resultFuture);
}
@@ -245,7 +245,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#requestFullBackupTime()}
*/
public long requestFullBackupTime() throws RemoteException {
- AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.requestFullBackupTime(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -309,7 +309,7 @@ public class BackupTransportClient {
*/
public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
throws RemoteException {
- AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Boolean> resultFuture = mTransportFutures.newFuture();
mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture);
Boolean result = getFutureResult(resultFuture);
return result != null && result;
@@ -319,7 +319,7 @@ public class BackupTransportClient {
* See {@link IBackupTransport#getBackupQuota(String, boolean)}
*/
public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
- AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -355,18 +355,58 @@ public class BackupTransportClient {
* See {@link IBackupTransport#getTransportFlags()}
*/
public int getTransportFlags() throws RemoteException {
- AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+ AndroidFuture<Integer> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getTransportFlags(resultFuture);
Integer result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
+ /**
+ * Allows the {@link TransportConnection} to notify this client
+ * if the underlying transport has become unusable. If that happens
+ * we want to cancel all active futures or callbacks.
+ */
+ void onBecomingUnusable() {
+ mCallbackPool.cancelActiveCallbacks();
+ mTransportFutures.cancelActiveFutures();
+ }
+
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
return future.get(600, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
return null;
+ } finally {
+ mTransportFutures.remove(future);
+ }
+ }
+
+ private static class TransportFutures {
+ private final Object mActiveFuturesLock = new Object();
+ private final Set<AndroidFuture<?>> mActiveFutures = new HashSet<>();
+
+ <T> AndroidFuture<T> newFuture() {
+ AndroidFuture<T> future = new AndroidFuture<>();
+ synchronized (mActiveFuturesLock) {
+ mActiveFutures.add(future);
+ }
+ return future;
+ }
+
+ <T> void remove(AndroidFuture<T> future) {
+ synchronized (mActiveFuturesLock) {
+ mActiveFutures.remove(future);
+ }
+ }
+
+ void cancelActiveFutures() {
+ synchronized (mActiveFuturesLock) {
+ for (AndroidFuture<?> future : mActiveFutures) {
+ future.cancel(true);
+ }
+ mActiveFutures.clear();
+ }
}
}
@@ -375,27 +415,47 @@ public class BackupTransportClient {
private final Object mPoolLock = new Object();
private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>();
+ private final Set<TransportStatusCallback> mActiveCallbacks = new HashSet<>();
TransportStatusCallback acquire() {
synchronized (mPoolLock) {
- if (mCallbackPool.isEmpty()) {
- return new TransportStatusCallback();
- } else {
- return mCallbackPool.poll();
+ TransportStatusCallback callback = mCallbackPool.poll();
+ if (callback == null) {
+ callback = new TransportStatusCallback();
}
+ callback.reset();
+ mActiveCallbacks.add(callback);
+ return callback;
}
}
void recycle(TransportStatusCallback callback) {
synchronized (mPoolLock) {
+ mActiveCallbacks.remove(callback);
if (mCallbackPool.size() > MAX_POOL_SIZE) {
Slog.d(TAG, "TransportStatusCallback pool size exceeded");
return;
}
-
- callback.reset();
mCallbackPool.add(callback);
}
}
+
+ void cancelActiveCallbacks() {
+ synchronized (mPoolLock) {
+ for (TransportStatusCallback callback : mActiveCallbacks) {
+ try {
+ callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR);
+ // This waits for status to propagate before the callback is reset.
+ callback.getOperationStatus();
+ } catch (RemoteException ex) {
+ // Nothing we can do.
+ }
+ if (mCallbackPool.size() < MAX_POOL_SIZE) {
+ mCallbackPool.add(callback);
+ }
+ }
+ mActiveCallbacks.clear();
+ }
+ }
}
}
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index f9a3c36a3220..1009787bebe5 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
@@ -449,6 +449,9 @@ public class TransportConnection {
private void onServiceDisconnected() {
synchronized (mStateLock) {
log(Priority.ERROR, "Service disconnected: client UNUSABLE");
+ if (mTransport != null) {
+ mTransport.onBecomingUnusable();
+ }
setStateLocked(State.UNUSABLE, null);
try {
// After unbindService() no calls back to mConnection
@@ -473,6 +476,9 @@ public class TransportConnection {
checkStateIntegrityLocked();
log(Priority.ERROR, "Binding died: client UNUSABLE");
+ if (mTransport != null) {
+ mTransport.onBecomingUnusable();
+ }
// After unbindService() no calls back to mConnection
switch (mState) {
case State.UNUSABLE:
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
index a55178c27eef..bc5cb0250d56 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -75,13 +75,11 @@ public class TransportStatusCallback extends ITransportStatusCallback.Stub {
}
Slog.w(TAG, "Couldn't get operation status from transport");
- return BackupTransport.TRANSPORT_ERROR;
} catch (InterruptedException e) {
Slog.w(TAG, "Couldn't get operation status from transport: ", e);
- return BackupTransport.TRANSPORT_ERROR;
- } finally {
- reset();
}
+
+ return BackupTransport.TRANSPORT_ERROR;
}
synchronized void reset() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6813f3f83135..1c10304f7223 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15623,24 +15623,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
for (int i = 0, size = processes.size(); i < size; i++) {
ProcessRecord app = processes.get(i);
- pw.println(String.format("------ DUMP RESOURCES %s (%s) ------",
+ pw.println(String.format("Resources History for %s (%s)",
app.processName,
app.info.packageName));
pw.flush();
try {
- TransferPipe tp = new TransferPipe();
+ TransferPipe tp = new TransferPipe(" ");
try {
IApplicationThread thread = app.getThread();
if (thread != null) {
app.getThread().dumpResources(tp.getWriteFd(), null);
tp.go(fd.getFileDescriptor(), 2000);
- pw.println(String.format("------ END DUMP RESOURCES %s (%s) ------",
- app.processName,
- app.info.packageName));
- pw.flush();
} else {
pw.println(String.format(
- "------ DUMP RESOURCES %s (%s) failed, no thread ------",
+ " Resources history for %s (%s) failed, no thread",
app.processName,
app.info.packageName));
}
@@ -15648,11 +15644,7 @@ public class ActivityManagerService extends IActivityManager.Stub
tp.kill();
}
} catch (IOException e) {
- pw.println(String.format(
- "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------",
- app.processName,
- app.info.packageName,
- e.getMessage()));
+ pw.println(" " + e.getMessage());
pw.flush();
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 3491cd59ebb7..49a935ebadc9 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -479,6 +479,8 @@ public class BtHelper {
}
if (profile == BluetoothProfile.A2DP) {
mA2dp = (BluetoothA2dp) proxy;
+ } else if (profile == BluetoothProfile.HEARING_AID) {
+ mHearingAid = (BluetoothHearingAid) proxy;
} else if (profile == BluetoothProfile.LE_AUDIO) {
mLeAudio = (BluetoothLeAudio) proxy;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ea054a5b280c..682f0dfe067c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2020,7 +2020,8 @@ public class Vpn {
.setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
- .setColor(mContext.getColor(R.color.system_notification_accent_color));
+ .setColor(mContext.getColor(
+ android.R.color.system_notification_accent_color));
notificationManager.notify(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, builder.build());
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b2f500a59ba9..d2d80ffde532 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,6 +408,11 @@ final class InputMethodBindingController {
@GuardedBy("ImfLock.class")
@NonNull
InputBindResult bindCurrentMethod() {
+ if (mSelectedMethodId == null) {
+ Slog.e(TAG, "mSelectedMethodId is null!");
+ return InputBindResult.NO_IME;
+ }
+
InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 8fdde2486401..e9bf90f1a82e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -16,8 +16,6 @@
package com.android.server.location.contexthub;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import android.Manifest;
import android.content.Context;
import android.hardware.contexthub.V1_0.AsyncEventType;
@@ -297,19 +295,14 @@ import java.util.List;
}
/**
- * Checks for location hardware permissions.
+ * Checks for ACCESS_CONTEXT_HUB permissions.
*
* @param context the context of the service
*/
/* package */
static void checkPermissions(Context context) {
- boolean hasAccessContextHubPermission = (context.checkCallingPermission(
- CONTEXT_HUB_PERMISSION) == PERMISSION_GRANTED);
-
- if (!hasAccessContextHubPermission) {
- throw new SecurityException(
- "ACCESS_CONTEXT_HUB permission required to use Context Hub");
- }
+ context.enforceCallingOrSelfPermission(CONTEXT_HUB_PERMISSION,
+ "ACCESS_CONTEXT_HUB permission required to use Context Hub");
}
/**
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 96391ac62530..074d891a9974 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -647,18 +647,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
if (mDestroyed) {
return;
}
- toSend = new ArrayList<>();
- if (mQueue != null) {
- toSend.ensureCapacity(mQueue.size());
- toSend.addAll(mQueue);
- }
+ toSend = mQueue == null ? null : new ArrayList<>(mQueue);
}
Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend);
- // Limit the size of initial Parcel to prevent binder buffer overflow
- // as onQueueChanged is an async binder call.
- parcelableQueue.setInlineCountLimit(1);
+ ParceledListSlice<QueueItem> parcelableQueue = null;
+ if (toSend != null) {
+ parcelableQueue = new ParceledListSlice<>(toSend);
+ // Limit the size of initial Parcel to prevent binder buffer overflow
+ // as onQueueChanged is an async binder call.
+ parcelableQueue.setInlineCountLimit(1);
+ }
try {
holder.mCallback.onQueueChanged(parcelableQueue);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index c09c904ff931..db0b0c58b046 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -183,7 +183,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -1596,18 +1595,16 @@ final class InstallPackageHelper {
parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
}
- // Check for shared user id changes
- if (!Objects.equals(oldPackage.getSharedUserId(),
- parsedPackage.getSharedUserId())
- // Don't mark as invalid if the app is trying to
- // leave a sharedUserId
- && parsedPackage.getSharedUserId() != null) {
+ // APK should not change its sharedUserId declarations
+ final var oldSharedUid = oldPackage.getSharedUserId() != null
+ ? oldPackage.getSharedUserId() : "<nothing>";
+ final var newSharedUid = parsedPackage.getSharedUserId() != null
+ ? parsedPackage.getSharedUserId() : "<nothing>";
+ if (!oldSharedUid.equals(newSharedUid)) {
throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
"Package " + parsedPackage.getPackageName()
+ " shared user changed from "
- + (oldPackage.getSharedUserId() != null
- ? oldPackage.getSharedUserId() : "<nothing>")
- + " to " + parsedPackage.getSharedUserId());
+ + oldSharedUid + " to " + newSharedUid);
}
// In case of rollback, remember per-user/profile install state
@@ -3696,10 +3693,13 @@ final class InstallPackageHelper {
}
disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(
parsedPackage.getPackageName());
- sharedUserSetting = (parsedPackage.getSharedUserId() != null)
- ? mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
- 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
- : null;
+ if (parsedPackage.getSharedUserId() != null && !parsedPackage.isLeavingSharedUid()) {
+ sharedUserSetting = mPm.mSettings.getSharedUserLPw(
+ parsedPackage.getSharedUserId(),
+ 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
+ } else {
+ sharedUserSetting = null;
+ }
if (DEBUG_PACKAGE_SCANNING
&& (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
&& sharedUserSetting != null) {
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index cdc2b1245b1e..f6f9faf98c40 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -290,6 +290,9 @@ public interface ParsingPackage extends ParsingPackageRead {
/** @see R#styleable.AndroidManifest_inheritKeyStoreKeys */
ParsingPackage setInheritKeyStoreKeys(boolean inheritKeyStoreKeys);
+ /** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */
+ ParsingPackage setLeavingSharedUid(boolean leavingSharedUid);
+
ParsingPackage setLabelRes(int labelRes);
ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 177eaca8e06f..67670272ef8b 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -549,6 +549,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
private static final long SDK_LIBRARY = 1L << 49;
private static final long INHERIT_KEYSTORE_KEYS = 1L << 50;
private static final long ENABLE_ON_BACK_INVOKED_CALLBACK = 1L << 51;
+ private static final long LEAVING_SHARED_UID = 1L << 52;
}
private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
@@ -2403,6 +2404,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
}
@Override
+ public boolean isLeavingSharedUid() {
+ return getBoolean(Booleans.LEAVING_SHARED_UID);
+ }
+
+ @Override
public ParsingPackageImpl setBaseRevisionCode(int value) {
baseRevisionCode = value;
return this;
@@ -2551,6 +2557,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden,
}
@Override
+ public ParsingPackageImpl setLeavingSharedUid(boolean value) {
+ return setBoolean(Booleans.LEAVING_SHARED_UID, value);
+ }
+
+ @Override
public ParsingPackageImpl setLabelRes(int value) {
labelRes = value;
return this;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
index 428374fa21a8..50033f652bfd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
@@ -360,4 +360,11 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt
* @see R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback
*/
boolean isOnBackInvokedCallbackEnabled();
+
+ /**
+ * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
+ * smaller than the current SDK version.
+ * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
+ */
+ boolean isLeavingSharedUid();
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f30daa930e6c..ed1ab01e1d12 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1032,11 +1032,6 @@ public class ParsingPackageUtils {
private static ParseResult<ParsingPackage> parseSharedUser(ParseInput input,
ParsingPackage pkg, TypedArray sa) {
- int maxSdkVersion = anInteger(0, R.styleable.AndroidManifest_sharedUserMaxSdkVersion, sa);
- if ((maxSdkVersion != 0) && maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT) {
- return input.success(pkg);
- }
-
String str = nonConfigString(0, R.styleable.AndroidManifest_sharedUserId, sa);
if (TextUtils.isEmpty(str)) {
return input.success(pkg);
@@ -1052,7 +1047,11 @@ public class ParsingPackageUtils {
}
}
+ int maxSdkVersion = anInteger(0, R.styleable.AndroidManifest_sharedUserMaxSdkVersion, sa);
+ boolean leaving = (maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT);
+
return input.success(pkg
+ .setLeavingSharedUid(leaving)
.setSharedUserId(str.intern())
.setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
new file mode 100644
index 000000000000..3550bda282dd
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -0,0 +1,148 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represent a step on a single vibrator that plays one or more segments from a
+ * {@link VibrationEffect.Composed} effect.
+ */
+abstract class AbstractVibratorStep extends Step {
+ public final VibratorController controller;
+ public final VibrationEffect.Composed effect;
+ public final int segmentIndex;
+ public final long previousStepVibratorOffTimeout;
+
+ long mVibratorOnResult;
+ boolean mVibratorCompleteCallbackReceived;
+
+ /**
+ * @param conductor The VibrationStepConductor for these steps.
+ * @param startTime The time to schedule this step in the
+ * {@link VibrationStepConductor}.
+ * @param controller The vibrator that is playing the effect.
+ * @param effect The effect being played in this step.
+ * @param index The index of the next segment to be played by this step
+ * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
+ * previous vibration and turn off. This is used to allow this step to
+ * be triggered when the completion callback is received, and can
+ * be used to play effects back-to-back.
+ */
+ AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long previousStepVibratorOffTimeout) {
+ super(conductor, startTime);
+ this.controller = controller;
+ this.effect = effect;
+ this.segmentIndex = index;
+ this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
+ }
+
+ public int getVibratorId() {
+ return controller.getVibratorInfo().getId();
+ }
+
+ @Override
+ public long getVibratorOnDuration() {
+ return mVibratorOnResult;
+ }
+
+ @Override
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
+ boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
+ mVibratorCompleteCallbackReceived |= isSameVibrator;
+ // Only activate this step if a timeout was set to wait for the vibration to complete,
+ // otherwise we are waiting for the correct time to play the next step.
+ return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
+ }
+
+ @Override
+ public List<Step> cancel() {
+ return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
+ /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
+ }
+
+ @Override
+ public void cancelImmediately() {
+ if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
+ // Vibrator might be running from previous steps, so turn it off while canceling.
+ stopVibrating();
+ }
+ }
+
+ protected void stopVibrating() {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Turning off vibrator " + getVibratorId());
+ }
+ controller.off();
+ }
+
+ protected void changeAmplitude(float amplitude) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
+ }
+ controller.setAmplitude(amplitude);
+ }
+
+ /**
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
+ * the segments.
+ */
+ protected List<Step> skipToNextSteps(int segmentsSkipped) {
+ return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
+ }
+
+ /**
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
+ * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
+ *
+ * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
+ * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
+ */
+ protected List<Step> nextSteps(int segmentsPlayed) {
+ if (mVibratorOnResult <= 0) {
+ // Vibration was not started, so just skip the played segments and keep timings.
+ return skipToNextSteps(segmentsPlayed);
+ }
+ long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
+ long nextVibratorOffTimeout =
+ nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
+ }
+
+ /**
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
+ * which might be calculated independently, jumping all played segments.
+ *
+ * <p>This should be used when the vibrator on/off state is not responsible for the steps
+ * execution timings, e.g. while playing the vibrator amplitudes.
+ */
+ protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
+ int segmentsPlayed) {
+ Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
+ segmentIndex + segmentsPlayed, vibratorOffTimeout);
+ return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
new file mode 100644
index 000000000000..8585e3473ef3
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -0,0 +1,114 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to complete a {@link VibrationEffect}.
+ *
+ * <p>This runs right at the time the vibration is considered to end and will update the pending
+ * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
+ */
+final class CompleteEffectVibratorStep extends AbstractVibratorStep {
+ private final boolean mCancelled;
+
+ CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
+ VibratorController controller, long previousStepVibratorOffTimeout) {
+ super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+ previousStepVibratorOffTimeout);
+ mCancelled = cancelled;
+ }
+
+ @Override
+ public boolean isCleanUp() {
+ // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
+ // Otherwise this step is part of the vibration.
+ return mCancelled;
+ }
+
+ @Override
+ public List<Step> cancel() {
+ if (mCancelled) {
+ // Double cancelling will just turn off the vibrator right away.
+ return Arrays.asList(
+ new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ }
+ return super.cancel();
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
+ try {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
+ + " step on vibrator " + controller.getVibratorInfo().getId());
+ }
+ if (mVibratorCompleteCallbackReceived) {
+ // Vibration completion callback was received by this step, just turn if off
+ // and skip any clean-up.
+ stopVibrating();
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ }
+
+ float currentAmplitude = controller.getCurrentAmplitude();
+ long remainingOnDuration =
+ previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
+ - SystemClock.uptimeMillis();
+ long rampDownDuration =
+ Math.min(remainingOnDuration,
+ conductor.vibrationSettings.getRampDownDuration());
+ long stepDownDuration = conductor.vibrationSettings.getRampStepDuration();
+ if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN
+ || rampDownDuration <= stepDownDuration) {
+ // No need to ramp down the amplitude, just wait to turn it off.
+ if (mCancelled) {
+ // Vibration is completing because it was cancelled, turn off right away.
+ stopVibrating();
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ } else {
+ return Arrays.asList(new TurnOffVibratorStep(
+ conductor, previousStepVibratorOffTimeout, controller));
+ }
+ }
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Ramping down vibrator " + controller.getVibratorInfo().getId()
+ + " from amplitude " + currentAmplitude
+ + " for " + rampDownDuration + "ms");
+ }
+ float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
+ float amplitudeTarget = currentAmplitude - amplitudeDelta;
+ long newVibratorOffTimeout =
+ mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
+ return Arrays.asList(
+ new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
+ controller, newVibratorOffTimeout));
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
new file mode 100644
index 000000000000..d1ea80557419
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of primitives.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link PrimitiveSegment} starting at the current index.
+ */
+final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+
+ ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long previousStepVibratorOffTimeout) {
+ // This step should wait for the last vibration to finish (with the timeout) and for the
+ // intended step start time (to respect the effect delays).
+ super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+ index, previousStepVibratorOffTimeout);
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
+ try {
+ // Load the next PrimitiveSegments to create a single compose call to the vibrator,
+ // limited to the vibrator composition maximum size.
+ int limit = controller.getVibratorInfo().getCompositionSizeMax();
+ int segmentCount = limit > 0
+ ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+ : effect.getSegments().size();
+ List<PrimitiveSegment> primitives = new ArrayList<>();
+ for (int i = segmentIndex; i < segmentCount; i++) {
+ VibrationEffectSegment segment = effect.getSegments().get(i);
+ if (segment instanceof PrimitiveSegment) {
+ primitives.add((PrimitiveSegment) segment);
+ } else {
+ break;
+ }
+ }
+
+ if (primitives.isEmpty()) {
+ Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
+ + effect.getSegments().get(segmentIndex));
+ return skipToNextSteps(/* segmentsSkipped= */ 1);
+ }
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
+ + controller.getVibratorInfo().getId());
+ }
+ mVibratorOnResult = controller.on(
+ primitives.toArray(new PrimitiveSegment[primitives.size()]),
+ getVibration().id);
+
+ return nextSteps(/* segmentsPlayed= */ primitives.size());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
new file mode 100644
index 000000000000..73bf933f8bc9
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of PWLE segments.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link StepSegment} or {@link RampSegment} starting at the current index.
+ */
+final class ComposePwleVibratorStep extends AbstractVibratorStep {
+
+ ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long previousStepVibratorOffTimeout) {
+ // This step should wait for the last vibration to finish (with the timeout) and for the
+ // intended step start time (to respect the effect delays).
+ super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+ index, previousStepVibratorOffTimeout);
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
+ try {
+ // Load the next RampSegments to create a single composePwle call to the vibrator,
+ // limited to the vibrator PWLE maximum size.
+ int limit = controller.getVibratorInfo().getPwleSizeMax();
+ int segmentCount = limit > 0
+ ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+ : effect.getSegments().size();
+ List<RampSegment> pwles = new ArrayList<>();
+ for (int i = segmentIndex; i < segmentCount; i++) {
+ VibrationEffectSegment segment = effect.getSegments().get(i);
+ if (segment instanceof RampSegment) {
+ pwles.add((RampSegment) segment);
+ } else {
+ break;
+ }
+ }
+
+ if (pwles.isEmpty()) {
+ Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
+ + effect.getSegments().get(segmentIndex));
+ return skipToNextSteps(/* segmentsSkipped= */ 1);
+ }
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+ + controller.getVibratorInfo().getId());
+ }
+ mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
+ getVibration().id);
+
+ return nextSteps(/* segmentsPlayed= */ pwles.size());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
new file mode 100644
index 000000000000..bbbca024214f
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -0,0 +1,73 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Finish a sync vibration started by a {@link StartSequentialEffectStep}.
+ *
+ * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+ * StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet.
+ */
+final class FinishSequentialEffectStep extends Step {
+ public final StartSequentialEffectStep startedStep;
+
+ FinishSequentialEffectStep(StartSequentialEffectStep startedStep) {
+ // No predefined startTime, just wait for all steps in the queue.
+ super(startedStep.conductor, Long.MAX_VALUE);
+ this.startedStep = startedStep;
+ }
+
+ @Override
+ public boolean isCleanUp() {
+ // This step only notes that all the vibrators has been turned off.
+ return true;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
+ try {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
+ }
+ conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+ Step nextStep = startedStep.nextStep();
+ return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
+ : Arrays.asList(nextStep);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public List<Step> cancel() {
+ cancelImmediately();
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ }
+
+ @Override
+ public void cancelImmediately() {
+ conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
new file mode 100644
index 000000000000..601ae978f637
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on with a single prebaked effect.
+ *
+ * <p>This step automatically falls back by replacing the prebaked segment with
+ * {@link VibrationSettings#getFallbackEffect(int)}, if available.
+ */
+final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
+
+ PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long previousStepVibratorOffTimeout) {
+ // This step should wait for the last vibration to finish (with the timeout) and for the
+ // intended step start time (to respect the effect delays).
+ super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+ index, previousStepVibratorOffTimeout);
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
+ try {
+ VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+ if (!(segment instanceof PrebakedSegment)) {
+ Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
+ + "PerformPrebakedVibratorStep: " + segment);
+ return skipToNextSteps(/* segmentsSkipped= */ 1);
+ }
+
+ PrebakedSegment prebaked = (PrebakedSegment) segment;
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG, "Perform " + VibrationEffect.effectIdToString(
+ prebaked.getEffectId()) + " on vibrator "
+ + controller.getVibratorInfo().getId());
+ }
+
+ VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
+ mVibratorOnResult = controller.on(prebaked, getVibration().id);
+
+ if (mVibratorOnResult == 0 && prebaked.shouldFallback()
+ && (fallback instanceof VibrationEffect.Composed)) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG, "Playing fallback for effect "
+ + VibrationEffect.effectIdToString(prebaked.getEffectId()));
+ }
+ AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
+ replaceCurrentSegment((VibrationEffect.Composed) fallback),
+ segmentIndex, previousStepVibratorOffTimeout);
+ List<Step> fallbackResult = fallbackStep.play();
+ // Update the result with the fallback result so this step is seamlessly
+ // replaced by the fallback to any outer application of this.
+ mVibratorOnResult = fallbackStep.getVibratorOnDuration();
+ return fallbackResult;
+ }
+
+ return nextSteps(/* segmentsPlayed= */ 1);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ /**
+ * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
+ *
+ * @return a copy of {@link #effect} with replaced segment.
+ */
+ private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
+ List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
+ int newRepeatIndex = effect.getRepeatIndex();
+ newSegments.remove(segmentIndex);
+ newSegments.addAll(segmentIndex, fallback.getSegments());
+ if (segmentIndex < effect.getRepeatIndex()) {
+ newRepeatIndex += fallback.getSegments().size() - 1;
+ }
+ return new VibrationEffect.Composed(newSegments, newRepeatIndex);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
new file mode 100644
index 000000000000..8cf5fb394d9d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Represents a step to ramp down the vibrator amplitude before turning it off. */
+final class RampOffVibratorStep extends AbstractVibratorStep {
+ private final float mAmplitudeTarget;
+ private final float mAmplitudeDelta;
+
+ RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
+ float amplitudeDelta, VibratorController controller,
+ long previousStepVibratorOffTimeout) {
+ super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+ previousStepVibratorOffTimeout);
+ mAmplitudeTarget = amplitudeTarget;
+ mAmplitudeDelta = amplitudeDelta;
+ }
+
+ @Override
+ public boolean isCleanUp() {
+ return true;
+ }
+
+ @Override
+ public List<Step> cancel() {
+ return Arrays.asList(
+ new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
+ try {
+ if (VibrationThread.DEBUG) {
+ long latency = SystemClock.uptimeMillis() - startTime;
+ Slog.d(VibrationThread.TAG, "Ramp down the vibrator amplitude, step with "
+ + latency + "ms latency.");
+ }
+ if (mVibratorCompleteCallbackReceived) {
+ // Vibration completion callback was received by this step, just turn if off
+ // and skip the rest of the steps to ramp down the vibrator amplitude.
+ stopVibrating();
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ }
+
+ changeAmplitude(mAmplitudeTarget);
+
+ float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
+ if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
+ // Vibrator amplitude cannot go further down, just turn it off.
+ return Arrays.asList(new TurnOffVibratorStep(
+ conductor, previousStepVibratorOffTimeout, controller));
+ }
+ return Arrays.asList(new RampOffVibratorStep(
+ conductor,
+ startTime + conductor.vibrationSettings.getRampStepDuration(),
+ newAmplitudeTarget, mAmplitudeDelta, controller,
+ previousStepVibratorOffTimeout));
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
new file mode 100644
index 000000000000..d5c11161bdfa
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -0,0 +1,186 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on and change its amplitude.
+ *
+ * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
+ * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
+ */
+final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+ private long mNextOffTime;
+
+ SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long previousStepVibratorOffTimeout) {
+ // This step has a fixed startTime coming from the timings of the waveform it's playing.
+ super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
+ mNextOffTime = previousStepVibratorOffTimeout;
+ }
+
+ @Override
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
+ if (controller.getVibratorInfo().getId() == vibratorId) {
+ mVibratorCompleteCallbackReceived = true;
+ mNextOffTime = SystemClock.uptimeMillis();
+ }
+ // Timings are tightly controlled here, so only trigger this step if the vibrator was
+ // supposed to be ON but has completed prematurely, to turn it back on as soon as
+ // possible.
+ return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
+ }
+
+ @Override
+ public List<Step> play() {
+ // TODO: consider separating the "on" steps at the start into a separate Step.
+ // TODO: consider instantiating the step with the required amplitude, rather than
+ // needing to dig into the effect.
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep");
+ try {
+ long now = SystemClock.uptimeMillis();
+ long latency = now - startTime;
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Running amplitude step with " + latency + "ms latency.");
+ }
+
+ if (mVibratorCompleteCallbackReceived && latency < 0) {
+ // This step was run early because the vibrator turned off prematurely.
+ // Turn it back on and return this same step to run at the exact right time.
+ mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+ return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
+ effect, segmentIndex, mNextOffTime));
+ }
+
+ VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+ if (!(segment instanceof StepSegment)) {
+ Slog.w(VibrationThread.TAG,
+ "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
+ return skipToNextSteps(/* segmentsSkipped= */ 1);
+ }
+
+ StepSegment stepSegment = (StepSegment) segment;
+ if (stepSegment.getDuration() == 0) {
+ // Skip waveform entries with zero timing.
+ return skipToNextSteps(/* segmentsSkipped= */ 1);
+ }
+
+ float amplitude = stepSegment.getAmplitude();
+ if (amplitude == 0) {
+ if (previousStepVibratorOffTimeout > now) {
+ // Amplitude cannot be set to zero, so stop the vibrator.
+ stopVibrating();
+ mNextOffTime = now;
+ }
+ } else {
+ if (startTime >= mNextOffTime) {
+ // Vibrator is OFF. Turn vibrator back on for the duration of another
+ // cycle before setting the amplitude.
+ long onDuration = getVibratorOnDuration(effect, segmentIndex);
+ if (onDuration > 0) {
+ mVibratorOnResult = startVibrating(onDuration);
+ mNextOffTime = now + onDuration
+ + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ }
+ }
+ changeAmplitude(amplitude);
+ }
+
+ // Use original startTime to avoid propagating latencies to the waveform.
+ long nextStartTime = startTime + segment.getDuration();
+ return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private long turnVibratorBackOn(long remainingDuration) {
+ long onDuration = getVibratorOnDuration(effect, segmentIndex);
+ if (onDuration <= 0) {
+ // Vibrator is supposed to go back off when this step starts, so just leave it off.
+ return previousStepVibratorOffTimeout;
+ }
+ onDuration += remainingDuration;
+ float expectedAmplitude = controller.getCurrentAmplitude();
+ mVibratorOnResult = startVibrating(onDuration);
+ if (mVibratorOnResult > 0) {
+ // Set the amplitude back to the value it was supposed to be playing at.
+ changeAmplitude(expectedAmplitude);
+ }
+ return SystemClock.uptimeMillis() + onDuration
+ + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ }
+
+ private long startVibrating(long duration) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ + duration + "ms");
+ }
+ return controller.on(duration, getVibration().id);
+ }
+
+ /**
+ * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
+ * until the next time it's vibrating amplitude is zero or a different type of segment is
+ * found.
+ */
+ private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+ List<VibrationEffectSegment> segments = effect.getSegments();
+ int segmentCount = segments.size();
+ int repeatIndex = effect.getRepeatIndex();
+ int i = startIndex;
+ long timing = 0;
+ while (i < segmentCount) {
+ VibrationEffectSegment segment = segments.get(i);
+ if (!(segment instanceof StepSegment)
+ || ((StepSegment) segment).getAmplitude() == 0) {
+ break;
+ }
+ timing += segment.getDuration();
+ i++;
+ if (i == segmentCount && repeatIndex >= 0) {
+ i = repeatIndex;
+ // prevent infinite loop
+ repeatIndex = -1;
+ }
+ if (i == startIndex) {
+ // The repeating waveform keeps the vibrator ON all the time. Use a minimum
+ // of 1s duration to prevent short patterns from turning the vibrator ON too
+ // frequently.
+ return Math.max(timing, 1000);
+ }
+ }
+ if (i == segmentCount && effect.getRepeatIndex() < 0) {
+ // Vibration ending at non-zero amplitude, add extra timings to ramp down after
+ // vibration is complete.
+ timing += conductor.vibrationSettings.getRampDownDuration();
+ }
+ return timing;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
new file mode 100644
index 000000000000..b8885e81dbe2
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -0,0 +1,369 @@
+/*
+ * 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.server.vibrator;
+
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Starts a sync vibration.
+ *
+ * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+ * add a {@link FinishSequentialEffectStep} to the queue, to be played after all vibrators
+ * have finished all their individual steps.
+ *
+ * <p>If this step does not start any vibrator, it will add a {@link StartSequentialEffectStep} if
+ * the sequential effect isn't finished yet.
+ *
+ * <p>TODO: this step actually does several things: multiple HAL calls to sync the vibrators,
+ * as well as dispatching the underlying vibrator instruction calls (which need to be done before
+ * triggering the synced effects). This role/encapsulation could probably be improved to split up
+ * the grouped HAL calls here, as well as to clarify the role of dispatching VibratorSteps between
+ * this class and the controller.
+ */
+final class StartSequentialEffectStep extends Step {
+ public final CombinedVibration.Sequential sequentialEffect;
+ public final int currentIndex;
+
+ private long mVibratorsOnMaxDuration;
+
+ StartSequentialEffectStep(VibrationStepConductor conductor,
+ CombinedVibration.Sequential effect) {
+ this(conductor, SystemClock.uptimeMillis() + effect.getDelays().get(0), effect,
+ /* index= */ 0);
+ }
+
+ StartSequentialEffectStep(VibrationStepConductor conductor, long startTime,
+ CombinedVibration.Sequential effect, int index) {
+ super(conductor, startTime);
+ sequentialEffect = effect;
+ currentIndex = index;
+ }
+
+ @Override
+ public long getVibratorOnDuration() {
+ return mVibratorsOnMaxDuration;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
+ List<Step> nextSteps = new ArrayList<>();
+ mVibratorsOnMaxDuration = -1;
+ try {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "StartSequentialEffectStep for effect #" + currentIndex);
+ }
+ CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
+ DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+ if (effectMapping == null) {
+ // Unable to map effects to vibrators, ignore this step.
+ return nextSteps;
+ }
+
+ mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
+ if (mVibratorsOnMaxDuration > 0) {
+ conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+ mVibratorsOnMaxDuration);
+ }
+ } finally {
+ if (mVibratorsOnMaxDuration >= 0) {
+ // It least one vibrator was started then add a finish step to wait for all
+ // active vibrators to finish their individual steps before going to the next.
+ // Otherwise this step was ignored so just go to the next one.
+ Step nextStep =
+ mVibratorsOnMaxDuration > 0 ? new FinishSequentialEffectStep(this)
+ : nextStep();
+ if (nextStep != null) {
+ nextSteps.add(nextStep);
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ return nextSteps;
+ }
+
+ @Override
+ public List<Step> cancel() {
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ }
+
+ @Override
+ public void cancelImmediately() {
+ }
+
+ /**
+ * Create the next {@link StartSequentialEffectStep} to play this sequential effect, starting at
+ * the
+ * time this method is called, or null if sequence is complete.
+ */
+ @Nullable
+ Step nextStep() {
+ int nextIndex = currentIndex + 1;
+ if (nextIndex >= sequentialEffect.getEffects().size()) {
+ return null;
+ }
+ long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+ long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+ return new StartSequentialEffectStep(conductor, nextStartTime, sequentialEffect,
+ nextIndex);
+ }
+
+ /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+ @Nullable
+ private DeviceEffectMap createEffectToVibratorMapping(
+ CombinedVibration effect) {
+ if (effect instanceof CombinedVibration.Mono) {
+ return new DeviceEffectMap((CombinedVibration.Mono) effect);
+ }
+ if (effect instanceof CombinedVibration.Stereo) {
+ return new DeviceEffectMap((CombinedVibration.Stereo) effect);
+ }
+ return null;
+ }
+
+ /**
+ * Starts playing effects on designated vibrators, in sync.
+ *
+ * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
+ * @param nextSteps An output list to accumulate the future {@link Step
+ * Steps} created
+ * by this method, typically one for each vibrator that has
+ * successfully started vibrating on this step.
+ * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
+ * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+ * have ignored all effects.
+ */
+ private long startVibrating(
+ DeviceEffectMap effectMapping, List<Step> nextSteps) {
+ int vibratorCount = effectMapping.size();
+ if (vibratorCount == 0) {
+ // No effect was mapped to any available vibrator.
+ return 0;
+ }
+
+ AbstractVibratorStep[] steps = new AbstractVibratorStep[vibratorCount];
+ long vibrationStartTime = SystemClock.uptimeMillis();
+ for (int i = 0; i < vibratorCount; i++) {
+ steps[i] = conductor.nextVibrateStep(vibrationStartTime,
+ conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
+ effectMapping.effectAt(i),
+ /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
+ }
+
+ if (steps.length == 1) {
+ // No need to prepare and trigger sync effects on a single vibrator.
+ return startVibrating(steps[0], nextSteps);
+ }
+
+ // This synchronization of vibrators should be executed one at a time, even if we are
+ // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
+ // one set of vibrators at a time.
+ // This property is guaranteed by there only being one thread (VibrationThread) executing
+ // one Step at a time, so there's no need to hold the state lock. Callbacks will be
+ // delivered asynchronously but enqueued until the step processing is finished.
+ boolean hasPrepared = false;
+ boolean hasTriggered = false;
+ long maxDuration = 0;
+ try {
+ hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
+ effectMapping.getRequiredSyncCapabilities(),
+ effectMapping.getVibratorIds());
+
+ for (AbstractVibratorStep step : steps) {
+ long duration = startVibrating(step, nextSteps);
+ if (duration < 0) {
+ // One vibrator has failed, fail this entire sync attempt.
+ return maxDuration = -1;
+ }
+ maxDuration = Math.max(maxDuration, duration);
+ }
+
+ // Check if sync was prepared and if any step was accepted by a vibrator,
+ // otherwise there is nothing to trigger here.
+ if (hasPrepared && maxDuration > 0) {
+ hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
+ getVibration().id);
+ }
+ return maxDuration;
+ } finally {
+ if (hasPrepared && !hasTriggered) {
+ // Trigger has failed or all steps were ignored by the vibrators.
+ conductor.vibratorManagerHooks.cancelSyncedVibration();
+ nextSteps.clear();
+ } else if (maxDuration < 0) {
+ // Some vibrator failed without being prepared so other vibrators might be
+ // active. Cancel and remove every pending step from output list.
+ for (int i = nextSteps.size() - 1; i >= 0; i--) {
+ nextSteps.remove(i).cancelImmediately();
+ }
+ }
+ }
+ }
+
+ private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
+ nextSteps.addAll(step.play());
+ long stepDuration = step.getVibratorOnDuration();
+ if (stepDuration < 0) {
+ // Step failed, so return negative duration to propagate failure.
+ return stepDuration;
+ }
+ // Return the longest estimation for the entire effect.
+ return Math.max(stepDuration, step.effect.getDuration());
+ }
+
+ /**
+ * Map a {@link CombinedVibration} to the vibrators available on the device.
+ *
+ * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+ * play all of the effects in sync.
+ */
+ final class DeviceEffectMap {
+ private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
+ private final int[] mVibratorIds;
+ private final long mRequiredSyncCapabilities;
+
+ DeviceEffectMap(CombinedVibration.Mono mono) {
+ SparseArray<VibratorController> vibrators = conductor.getVibrators();
+ mVibratorEffects = new SparseArray<>(vibrators.size());
+ mVibratorIds = new int[vibrators.size()];
+ for (int i = 0; i < vibrators.size(); i++) {
+ int vibratorId = vibrators.keyAt(i);
+ VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+ VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+ mono.getEffect(), vibratorInfo);
+ if (effect instanceof VibrationEffect.Composed) {
+ mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+ mVibratorIds[i] = vibratorId;
+ }
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ DeviceEffectMap(CombinedVibration.Stereo stereo) {
+ SparseArray<VibratorController> vibrators = conductor.getVibrators();
+ SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+ mVibratorEffects = new SparseArray<>();
+ for (int i = 0; i < stereoEffects.size(); i++) {
+ int vibratorId = stereoEffects.keyAt(i);
+ if (vibrators.contains(vibratorId)) {
+ VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+ VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+ stereoEffects.valueAt(i), vibratorInfo);
+ if (effect instanceof VibrationEffect.Composed) {
+ mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+ }
+ }
+ }
+ mVibratorIds = new int[mVibratorEffects.size()];
+ for (int i = 0; i < mVibratorEffects.size(); i++) {
+ mVibratorIds[i] = mVibratorEffects.keyAt(i);
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ /**
+ * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
+ * device.
+ */
+ public int size() {
+ return mVibratorIds.length;
+ }
+
+ /**
+ * Return all capabilities required to play the {@link CombinedVibration} in
+ * between calls to {@link IVibratorManager#prepareSynced} and
+ * {@link IVibratorManager#triggerSynced}.
+ */
+ public long getRequiredSyncCapabilities() {
+ return mRequiredSyncCapabilities;
+ }
+
+ /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
+ public int[] getVibratorIds() {
+ return mVibratorIds;
+ }
+
+ /** Return the id of the vibrator at given index. */
+ public int vibratorIdAt(int index) {
+ return mVibratorEffects.keyAt(index);
+ }
+
+ /** Return the {@link VibrationEffect} at given index. */
+ public VibrationEffect.Composed effectAt(int index) {
+ return mVibratorEffects.valueAt(index);
+ }
+
+ /**
+ * Return all capabilities required from the {@link IVibratorManager} to prepare and
+ * trigger all given effects in sync.
+ *
+ * @return {@link IVibratorManager#CAP_SYNC} together with all required
+ * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
+ */
+ private long calculateRequiredSyncCapabilities(
+ SparseArray<VibrationEffect.Composed> effects) {
+ long prepareCap = 0;
+ for (int i = 0; i < effects.size(); i++) {
+ VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
+ if (firstSegment instanceof StepSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+ } else if (firstSegment instanceof PrebakedSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+ } else if (firstSegment instanceof PrimitiveSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+ }
+ }
+ int triggerCap = 0;
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
+ }
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+ }
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+ }
+ return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
+ }
+
+ /**
+ * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
+ * different ones, requiring a mixed trigger capability from the vibrator manager for
+ * syncing all effects.
+ */
+ private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
+ return (prepareCapabilities & capability) != 0
+ && (prepareCapabilities & ~capability) != 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java
new file mode 100644
index 000000000000..042e8a0e6ad5
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/Step.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represent a single step for playing a vibration.
+ *
+ * <p>Every step has a start time, which can be used to apply delays between steps while
+ * executing them in sequence.
+ */
+abstract class Step implements Comparable<Step> {
+ public final VibrationStepConductor conductor;
+ public final long startTime;
+
+ Step(VibrationStepConductor conductor, long startTime) {
+ this.conductor = conductor;
+ this.startTime = startTime;
+ }
+
+ protected Vibration getVibration() {
+ return conductor.getVibration();
+ }
+
+ /**
+ * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
+ * {@link CombinedVibration}.
+ */
+ public boolean isCleanUp() {
+ return false;
+ }
+
+ /** Play this step, returning a (possibly empty) list of next steps. */
+ @NonNull
+ public abstract List<Step> play();
+
+ /**
+ * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
+ * be played to gracefully cancel this step.
+ */
+ @NonNull
+ public abstract List<Step> cancel();
+
+ /** Cancel this pending step immediately, skipping any clean-up. */
+ public abstract void cancelImmediately();
+
+ /**
+ * Return the duration the vibrator was turned on when this step was played.
+ *
+ * @return A positive duration that the vibrator was turned on for by this step;
+ * Zero if the segment is not supported, the step was not played yet or vibrator was never
+ * turned on by this step; A negative value if the vibrator call has failed.
+ */
+ public long getVibratorOnDuration() {
+ return 0;
+ }
+
+ /**
+ * Return true to run this step right after a vibrator has notified vibration completed,
+ * used to resume steps waiting on vibrator callbacks with a timeout.
+ */
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
+ return false;
+ }
+
+ /**
+ * Returns the time in millis to wait before playing this step. This is performed
+ * while holding the queue lock, so should not rely on potentially slow operations.
+ */
+ public long calculateWaitTime() {
+ if (startTime == Long.MAX_VALUE) {
+ // This step don't have a predefined start time, it's just marked to be executed
+ // after all other steps have finished.
+ return 0;
+ }
+ return Math.max(0, startTime - SystemClock.uptimeMillis());
+ }
+
+ @Override
+ public int compareTo(Step o) {
+ return Long.compare(startTime, o.startTime);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
new file mode 100644
index 000000000000..297ef5614e84
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator off.
+ *
+ * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+ * and can be brought forward by vibrator complete callbacks. The step shouldn't be skipped, even
+ * if the vibrator-complete callback was received, as some implementations still rely on the
+ * "off" call to actually stop.
+ */
+final class TurnOffVibratorStep extends AbstractVibratorStep {
+
+ TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller) {
+ super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+ }
+
+ @Override
+ public boolean isCleanUp() {
+ return true;
+ }
+
+ @Override
+ public List<Step> cancel() {
+ return Arrays.asList(
+ new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ }
+
+ @Override
+ public void cancelImmediately() {
+ stopVibrating();
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
+ try {
+ stopVibrating();
+ return VibrationStepConductor.EMPTY_STEP_LIST;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
new file mode 100644
index 000000000000..51691fbcdf5a
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -0,0 +1,355 @@
+/*
+ * 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.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.VibrationEffect;
+import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
+ * dispatch of callbacks.
+ */
+final class VibrationStepConductor {
+ /**
+ * Extra timeout added to the end of each vibration step to ensure it finishes even when
+ * vibrator callbacks are lost.
+ */
+ static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
+ /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
+ static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
+ static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
+ final Object mLock = new Object();
+
+ // Used within steps.
+ public final VibrationSettings vibrationSettings;
+ public final DeviceVibrationEffectAdapter deviceEffectAdapter;
+ public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+
+ private final WorkSource mWorkSource;
+ private final Vibration mVibration;
+ private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
+ @GuardedBy("mLock")
+ private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+ @GuardedBy("mLock")
+ private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
+
+ @GuardedBy("mLock")
+ private int mPendingVibrateSteps;
+ @GuardedBy("mLock")
+ private int mConsumedStartVibrateSteps;
+ @GuardedBy("mLock")
+ private int mSuccessfulVibratorOnSteps;
+ @GuardedBy("mLock")
+ private boolean mWaitToProcessVibratorCompleteCallbacks;
+
+ VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
+ DeviceVibrationEffectAdapter effectAdapter,
+ SparseArray<VibratorController> availableVibrators,
+ VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
+ this.mVibration = vib;
+ this.vibrationSettings = vibrationSettings;
+ this.deviceEffectAdapter = effectAdapter;
+ this.vibratorManagerHooks = vibratorManagerHooks;
+ this.mWorkSource = new WorkSource(mVibration.uid);
+
+ CombinedVibration effect = vib.getEffect();
+ for (int i = 0; i < availableVibrators.size(); i++) {
+ if (effect.hasVibrator(availableVibrators.keyAt(i))) {
+ mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
+ }
+ }
+ }
+
+ @Nullable
+ AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
+ VibrationEffect.Composed effect, int segmentIndex,
+ long previousStepVibratorOffTimeout) {
+ if (segmentIndex >= effect.getSegments().size()) {
+ segmentIndex = effect.getRepeatIndex();
+ }
+ if (segmentIndex < 0) {
+ // No more segments to play, last step is to complete the vibration on this vibrator.
+ return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
+ controller, previousStepVibratorOffTimeout);
+ }
+
+ VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+ if (segment instanceof PrebakedSegment) {
+ return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
+ segmentIndex, previousStepVibratorOffTimeout);
+ }
+ if (segment instanceof PrimitiveSegment) {
+ return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
+ segmentIndex, previousStepVibratorOffTimeout);
+ }
+ if (segment instanceof RampSegment) {
+ return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
+ previousStepVibratorOffTimeout);
+ }
+ return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
+ previousStepVibratorOffTimeout);
+ }
+
+ public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
+ synchronized (mLock) {
+ mPendingVibrateSteps++;
+ mNextSteps.offer(new StartSequentialEffectStep(this, vibration));
+ }
+ }
+
+ public Vibration getVibration() {
+ return mVibration;
+ }
+
+ public WorkSource getWorkSource() {
+ return mWorkSource;
+ }
+
+ SparseArray<VibratorController> getVibrators() {
+ return mVibrators;
+ }
+
+ public boolean isFinished() {
+ synchronized (mLock) {
+ return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
+ }
+ }
+
+ /**
+ * Calculate the {@link Vibration.Status} based on the current queue state and the expected
+ * number of {@link StartSequentialEffectStep} to be played.
+ */
+ public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
+ synchronized (mLock) {
+ if (mPendingVibrateSteps > 0
+ || mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
+ return Vibration.Status.RUNNING;
+ }
+ if (mSuccessfulVibratorOnSteps > 0) {
+ return Vibration.Status.FINISHED;
+ }
+ // If no step was able to turn the vibrator ON successfully.
+ return Vibration.Status.IGNORED_UNSUPPORTED;
+ }
+ }
+
+ /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
+ @GuardedBy("mLock")
+ public long getWaitMillisBeforeNextStepLocked() {
+ if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+ // Steps resumed by vibrator complete callback should be played right away.
+ return 0;
+ }
+ Step nextStep = mNextSteps.peek();
+ return nextStep == null ? 0 : nextStep.calculateWaitTime();
+ }
+
+ /**
+ * Play and remove the step at the top of this queue, and also adds the next steps generated
+ * to be played next.
+ */
+ public void runNextStep() {
+ // Vibrator callbacks should wait until the polled step is played and the next steps are
+ // added back to the queue, so they can handle the callback.
+ markWaitToProcessVibratorCallbacks();
+ try {
+ Step nextStep = pollNext();
+ if (nextStep != null) {
+ // This might turn on the vibrator and have a HAL latency. Execute this outside
+ // any lock to avoid blocking other interactions with the thread.
+ List<Step> nextSteps = nextStep.play();
+ synchronized (mLock) {
+ if (nextStep.getVibratorOnDuration() > 0) {
+ mSuccessfulVibratorOnSteps++;
+ }
+ if (nextStep instanceof StartSequentialEffectStep) {
+ mConsumedStartVibrateSteps++;
+ }
+ if (!nextStep.isCleanUp()) {
+ mPendingVibrateSteps--;
+ }
+ for (int i = 0; i < nextSteps.size(); i++) {
+ mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
+ }
+ mNextSteps.addAll(nextSteps);
+ }
+ }
+ } finally {
+ synchronized (mLock) {
+ processVibratorCompleteCallbacksLocked();
+ }
+ }
+ }
+
+ /**
+ * Notify the vibrator completion.
+ *
+ * <p>This is a lightweight method that do not trigger any operation from {@link
+ * VibratorController}, so it can be called directly from a native callback.
+ */
+ @GuardedBy("mLock")
+ private void notifyVibratorCompleteLocked(int vibratorId) {
+ mCompletionNotifiedVibrators.offer(vibratorId);
+ if (!mWaitToProcessVibratorCompleteCallbacks) {
+ // No step is being played or cancelled now, process the callback right away.
+ processVibratorCompleteCallbacksLocked();
+ }
+ }
+
+ public void notifyVibratorComplete(int vibratorId) {
+ synchronized (mLock) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Vibration complete reported by vibrator " + vibratorId);
+ }
+ notifyVibratorCompleteLocked(vibratorId);
+ mLock.notify();
+ }
+ }
+
+ public void notifySyncedVibrationComplete() {
+ synchronized (mLock) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Synced vibration complete reported by vibrator manager");
+ }
+ for (int i = 0; i < mVibrators.size(); i++) {
+ notifyVibratorCompleteLocked(mVibrators.keyAt(i));
+ }
+ mLock.notify();
+ }
+ }
+
+ /**
+ * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
+ *
+ * <p>This will remove all steps and replace them with respective
+ * {@link Step#cancel()}.
+ */
+ public void cancel() {
+ // Vibrator callbacks should wait until all steps from the queue are properly cancelled
+ // and clean up steps are added back to the queue, so they can handle the callback.
+ markWaitToProcessVibratorCallbacks();
+ try {
+ List<Step> cleanUpSteps = new ArrayList<>();
+ Step step;
+ while ((step = pollNext()) != null) {
+ cleanUpSteps.addAll(step.cancel());
+ }
+ synchronized (mLock) {
+ // All steps generated by Step.cancel() should be clean-up steps.
+ mPendingVibrateSteps = 0;
+ mNextSteps.addAll(cleanUpSteps);
+ }
+ } finally {
+ synchronized (mLock) {
+ processVibratorCompleteCallbacksLocked();
+ }
+ }
+ }
+
+ /**
+ * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
+ *
+ * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
+ */
+ public void cancelImmediately() {
+ // Vibrator callbacks should wait until all steps from the queue are properly cancelled.
+ markWaitToProcessVibratorCallbacks();
+ try {
+ Step step;
+ while ((step = pollNext()) != null) {
+ // This might turn off the vibrator and have a HAL latency. Execute this outside
+ // any lock to avoid blocking other interactions with the thread.
+ step.cancelImmediately();
+ }
+ synchronized (mLock) {
+ mPendingVibrateSteps = 0;
+ }
+ } finally {
+ synchronized (mLock) {
+ processVibratorCompleteCallbacksLocked();
+ }
+ }
+ }
+
+ @Nullable
+ private Step pollNext() {
+ synchronized (mLock) {
+ // Prioritize the steps resumed by a vibrator complete callback.
+ if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+ return mPendingOnVibratorCompleteSteps.poll();
+ }
+ return mNextSteps.poll();
+ }
+ }
+
+ private void markWaitToProcessVibratorCallbacks() {
+ synchronized (mLock) {
+ mWaitToProcessVibratorCompleteCallbacks = true;
+ }
+ }
+
+ /**
+ * Notify the step in this queue that should be resumed by the vibrator completion
+ * callback and keep it separate to be consumed by {@link #runNextStep()}.
+ *
+ * <p>This is a lightweight method that do not trigger any operation from {@link
+ * VibratorController}, so it can be called directly from a native callback.
+ *
+ * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+ * first step found will be resumed by this method, in no particular order.
+ */
+ @GuardedBy("mLock")
+ private void processVibratorCompleteCallbacksLocked() {
+ mWaitToProcessVibratorCompleteCallbacks = false;
+ while (!mCompletionNotifiedVibrators.isEmpty()) {
+ int vibratorId = mCompletionNotifiedVibrators.poll();
+ Iterator<Step> it = mNextSteps.iterator();
+ while (it.hasNext()) {
+ Step step = it.next();
+ if (step.acceptVibratorCompleteCallback(vibratorId)) {
+ it.remove();
+ mPendingOnVibratorCompleteSteps.offer(step);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 1f1f40b8121d..f2cd8c3ec3f8 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,59 +16,23 @@
package com.android.server.vibrator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.WorkSource;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.FrameworkStatsLog;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
import java.util.NoSuchElementException;
-import java.util.PriorityQueue;
-import java.util.Queue;
/** Plays a {@link Vibration} in dedicated thread. */
final class VibrationThread extends Thread implements IBinder.DeathRecipient {
- private static final String TAG = "VibrationThread";
- private static final boolean DEBUG = false;
-
- /**
- * Extra timeout added to the end of each vibration step to ensure it finishes even when
- * vibrator callbacks are lost.
- */
- private static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
-
- /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
- private static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
-
- /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
- private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
-
- private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+ static final String TAG = "VibrationThread";
+ static final boolean DEBUG = false;
/** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
interface VibratorManagerHooks {
@@ -94,6 +58,15 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
void cancelSyncedVibration();
/**
+ * Record that a vibrator was turned on, and may remain on for the specified duration,
+ * on behalf of the given uid.
+ */
+ void noteVibratorOn(int uid, long duration);
+
+ /** Record that a vibrator was turned off, on behalf of the given uid. */
+ void noteVibratorOff(int uid);
+
+ /**
* Tell the manager that the currently active vibration has completed its vibration, from
* the perspective of the Effect. However, the VibrationThread may still be continuing with
* cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
@@ -108,16 +81,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
void onVibrationThreadReleased();
}
- private final Object mLock = new Object();
- private final WorkSource mWorkSource;
private final PowerManager.WakeLock mWakeLock;
- private final IBatteryStats mBatteryStatsService;
- private final VibrationSettings mVibrationSettings;
- private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
- private final Vibration mVibration;
- private final VibratorManagerHooks mVibratorManagerHooks;
- private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
- private final StepQueue mStepQueue = new StepQueue();
+ private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks;
+
+ private final VibrationStepConductor mStepConductor;
private volatile boolean mStop;
private volatile boolean mForceStop;
@@ -127,30 +94,20 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
- IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) {
- mVibration = vib;
- mVibrationSettings = vibrationSettings;
- mDeviceEffectAdapter = effectAdapter;
+ VibratorManagerHooks vibratorManagerHooks) {
mVibratorManagerHooks = vibratorManagerHooks;
- mWorkSource = new WorkSource(mVibration.uid);
mWakeLock = wakeLock;
- mBatteryStatsService = batteryStatsService;
-
- CombinedVibration effect = vib.getEffect();
- for (int i = 0; i < availableVibrators.size(); i++) {
- if (effect.hasVibrator(availableVibrators.keyAt(i))) {
- mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
- }
- }
+ mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter,
+ availableVibrators, vibratorManagerHooks);
}
Vibration getVibration() {
- return mVibration;
+ return mStepConductor.getVibration();
}
@VisibleForTesting
SparseArray<VibratorController> getVibrators() {
- return mVibrators;
+ return mStepConductor.getVibrators();
}
@Override
@@ -179,7 +136,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
/** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
private void runWithWakeLock() {
- mWakeLock.setWorkSource(mWorkSource);
+ mWakeLock.setWorkSource(mStepConductor.getWorkSource());
mWakeLock.acquire();
try {
runWithWakeLockAndDeathLink();
@@ -193,8 +150,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
* Called from within runWithWakeLock.
*/
private void runWithWakeLockAndDeathLink() {
+ IBinder vibrationBinderToken = mStepConductor.getVibration().token;
try {
- mVibration.token.linkToDeath(this, 0);
+ vibrationBinderToken.linkToDeath(this, 0);
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
@@ -206,7 +164,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
playVibration();
} finally {
try {
- mVibration.token.unlinkToDeath(this, 0);
+ vibrationBinderToken.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
Slog.wtf(TAG, "Failed to unlink token", e);
}
@@ -220,11 +178,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
return;
}
mStop = true;
- synchronized (mLock) {
+ synchronized (mStepConductor.mLock) {
if (DEBUG) {
Slog.d(TAG, "Vibration cancelled");
}
- mLock.notify();
+ mStepConductor.mLock.notify();
}
}
@@ -235,36 +193,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
return;
}
mStop = mForceStop = true;
- synchronized (mLock) {
+ synchronized (mStepConductor.mLock) {
if (DEBUG) {
Slog.d(TAG, "Vibration cancelled immediately");
}
- mLock.notify();
+ mStepConductor.mLock.notify();
}
}
/** Notify current vibration that a synced step has completed. */
public void syncedVibrationComplete() {
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
- }
- for (int i = 0; i < mVibrators.size(); i++) {
- mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i));
- }
- mLock.notify();
- }
+ mStepConductor.notifySyncedVibrationComplete();
}
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
- }
- mStepQueue.notifyVibratorComplete(vibratorId);
- mLock.notify();
- }
+ mStepConductor.notifyVibratorComplete(vibratorId);
}
// Indicate that the vibration is complete. This can be called multiple times only for
@@ -273,52 +217,55 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
- mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus);
+ mVibratorManagerHooks.onVibrationCompleted(
+ mStepConductor.getVibration().id, completedStatus);
}
}
private void playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
- CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
+ CombinedVibration.Sequential sequentialEffect =
+ toSequential(mStepConductor.getVibration().getEffect());
final int sequentialEffectSize = sequentialEffect.getEffects().size();
- mStepQueue.initializeForEffect(sequentialEffect);
+ mStepConductor.initializeForEffect(sequentialEffect);
- while (!mStepQueue.isFinished()) {
+ while (!mStepConductor.isFinished()) {
long waitMillisBeforeNextStep;
- synchronized (mLock) {
- waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep();
+ synchronized (mStepConductor.mLock) {
+ waitMillisBeforeNextStep = mStepConductor.getWaitMillisBeforeNextStepLocked();
if (waitMillisBeforeNextStep > 0) {
try {
- mLock.wait(waitMillisBeforeNextStep);
+ mStepConductor.mLock.wait(waitMillisBeforeNextStep);
} catch (InterruptedException e) {
}
}
}
// Only run the next vibration step if we didn't have to wait in this loop.
- // If we waited then the queue may have changed, so loop again to re-evaluate
- // the scheduling of the queue top element.
+ // If we waited then the queue may have changed or the wait could have been
+ // interrupted by a cancel call, so loop again to re-evaluate the scheduling of
+ // the queue top element.
if (waitMillisBeforeNextStep <= 0) {
if (DEBUG) {
Slog.d(TAG, "Play vibration consuming next step...");
}
// Run the step without holding the main lock, to avoid HAL interactions from
// blocking the thread.
- mStepQueue.runNextStep();
+ mStepConductor.runNextStep();
}
Vibration.Status status = mStop ? Vibration.Status.CANCELLED
- : mStepQueue.calculateVibrationStatus(sequentialEffectSize);
+ : mStepConductor.calculateVibrationStatus(sequentialEffectSize);
if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
// First time vibration stopped running, start clean-up tasks and notify
// callback immediately.
clientVibrationCompleteIfNotAlready(status);
if (status == Vibration.Status.CANCELLED) {
- mStepQueue.cancel();
+ mStepConductor.cancel();
}
}
if (mForceStop) {
// Cancel every step and stop playing them right away, even clean-up steps.
- mStepQueue.cancelImmediately();
+ mStepConductor.cancelImmediately();
clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
break;
}
@@ -328,61 +275,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
}
}
- private void noteVibratorOn(long duration) {
- try {
- if (duration <= 0) {
- return;
- }
- if (duration == Long.MAX_VALUE) {
- // Repeating duration has started. Report a fixed duration here, noteVibratorOff
- // should be called when this is cancelled.
- duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
- }
- mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
- duration);
- } catch (RemoteException e) {
- }
- }
-
- private void noteVibratorOff() {
- try {
- mBatteryStatsService.noteVibratorOff(mVibration.uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
- /* duration= */ 0);
- } catch (RemoteException e) {
- }
- }
-
- @Nullable
- private SingleVibratorStep nextVibrateStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int segmentIndex, long vibratorOffTimeout) {
- if (segmentIndex >= effect.getSegments().size()) {
- segmentIndex = effect.getRepeatIndex();
- }
- if (segmentIndex < 0) {
- // No more segments to play, last step is to complete the vibration on this vibrator.
- return new EffectCompleteStep(startTime, /* cancelled= */ false, controller,
- vibratorOffTimeout);
- }
-
- VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
- if (segment instanceof PrebakedSegment) {
- return new PerformStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
- }
- if (segment instanceof PrimitiveSegment) {
- return new ComposePrimitivesStep(startTime, controller, effect, segmentIndex,
- vibratorOffTimeout);
- }
- if (segment instanceof RampSegment) {
- return new ComposePwleStep(startTime, controller, effect, segmentIndex,
- vibratorOffTimeout);
- }
- return new AmplitudeStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
- }
-
private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
if (effect instanceof CombinedVibration.Sequential) {
return (CombinedVibration.Sequential) effect;
@@ -392,1268 +284,4 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
.combine();
}
- /** Queue for {@link Step Steps}, sorted by their start time. */
- private final class StepQueue {
- @GuardedBy("mLock")
- private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
- @GuardedBy("mLock")
- private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
- @GuardedBy("mLock")
- private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
-
- @GuardedBy("mLock")
- private int mPendingVibrateSteps;
- @GuardedBy("mLock")
- private int mConsumedStartVibrateSteps;
- @GuardedBy("mLock")
- private int mSuccessfulVibratorOnSteps;
- @GuardedBy("mLock")
- private boolean mWaitToProcessVibratorCompleteCallbacks;
-
- public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
- synchronized (mLock) {
- mPendingVibrateSteps++;
- mNextSteps.offer(new StartVibrateStep(vibration));
- }
- }
-
- public boolean isFinished() {
- synchronized (mLock) {
- return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
- }
- }
-
- /**
- * Calculate the {@link Vibration.Status} based on the current queue state and the expected
- * number of {@link StartVibrateStep} to be played.
- */
- public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
- synchronized (mLock) {
- if (mPendingVibrateSteps > 0
- || mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
- return Vibration.Status.RUNNING;
- }
- if (mSuccessfulVibratorOnSteps > 0) {
- return Vibration.Status.FINISHED;
- }
- // If no step was able to turn the vibrator ON successfully.
- return Vibration.Status.IGNORED_UNSUPPORTED;
- }
- }
-
- /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
- @GuardedBy("VibrationThread.this.mLock")
- public long getWaitMillisBeforeNextStep() {
- if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
- // Steps resumed by vibrator complete callback should be played right away.
- return 0;
- }
- Step nextStep = mNextSteps.peek();
- return nextStep == null ? 0 : nextStep.calculateWaitTime();
- }
-
- /**
- * Play and remove the step at the top of this queue, and also adds the next steps generated
- * to be played next.
- */
- public void runNextStep() {
- // Vibrator callbacks should wait until the polled step is played and the next steps are
- // added back to the queue, so they can handle the callback.
- markWaitToProcessVibratorCallbacks();
- try {
- Step nextStep = pollNext();
- if (nextStep != null) {
- // This might turn on the vibrator and have a HAL latency. Execute this outside
- // any lock to avoid blocking other interactions with the thread.
- List<Step> nextSteps = nextStep.play();
- synchronized (mLock) {
- if (nextStep.getVibratorOnDuration() > 0) {
- mSuccessfulVibratorOnSteps++;
- }
- if (nextStep instanceof StartVibrateStep) {
- mConsumedStartVibrateSteps++;
- }
- if (!nextStep.isCleanUp()) {
- mPendingVibrateSteps--;
- }
- for (int i = 0; i < nextSteps.size(); i++) {
- mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
- }
- mNextSteps.addAll(nextSteps);
- }
- }
- } finally {
- synchronized (mLock) {
- processVibratorCompleteCallbacks();
- }
- }
- }
-
- /**
- * Notify the vibrator completion.
- *
- * <p>This is a lightweight method that do not trigger any operation from {@link
- * VibratorController}, so it can be called directly from a native callback.
- */
- @GuardedBy("mLock")
- public void notifyVibratorComplete(int vibratorId) {
- mCompletionNotifiedVibrators.offer(vibratorId);
- if (!mWaitToProcessVibratorCompleteCallbacks) {
- // No step is being played or cancelled now, process the callback right away.
- processVibratorCompleteCallbacks();
- }
- }
-
- /**
- * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
- *
- * <p>This will remove all steps and replace them with respective
- * {@link Step#cancel()}.
- */
- public void cancel() {
- // Vibrator callbacks should wait until all steps from the queue are properly cancelled
- // and clean up steps are added back to the queue, so they can handle the callback.
- markWaitToProcessVibratorCallbacks();
- try {
- List<Step> cleanUpSteps = new ArrayList<>();
- Step step;
- while ((step = pollNext()) != null) {
- cleanUpSteps.addAll(step.cancel());
- }
- synchronized (mLock) {
- // All steps generated by Step.cancel() should be clean-up steps.
- mPendingVibrateSteps = 0;
- mNextSteps.addAll(cleanUpSteps);
- }
- } finally {
- synchronized (mLock) {
- processVibratorCompleteCallbacks();
- }
- }
- }
-
- /**
- * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
- *
- * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
- */
- public void cancelImmediately() {
- // Vibrator callbacks should wait until all steps from the queue are properly cancelled.
- markWaitToProcessVibratorCallbacks();
- try {
- Step step;
- while ((step = pollNext()) != null) {
- // This might turn off the vibrator and have a HAL latency. Execute this outside
- // any lock to avoid blocking other interactions with the thread.
- step.cancelImmediately();
- }
- synchronized (mLock) {
- mPendingVibrateSteps = 0;
- }
- } finally {
- synchronized (mLock) {
- processVibratorCompleteCallbacks();
- }
- }
- }
-
- @Nullable
- private Step pollNext() {
- synchronized (mLock) {
- // Prioritize the steps resumed by a vibrator complete callback.
- if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
- return mPendingOnVibratorCompleteSteps.poll();
- }
- return mNextSteps.poll();
- }
- }
-
- private void markWaitToProcessVibratorCallbacks() {
- synchronized (mLock) {
- mWaitToProcessVibratorCompleteCallbacks = true;
- }
- }
-
- /**
- * Notify the step in this queue that should be resumed by the vibrator completion
- * callback and keep it separate to be consumed by {@link #runNextStep()}.
- *
- * <p>This is a lightweight method that do not trigger any operation from {@link
- * VibratorController}, so it can be called directly from a native callback.
- *
- * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
- * first step found will be resumed by this method, in no particular order.
- */
- @GuardedBy("mLock")
- private void processVibratorCompleteCallbacks() {
- mWaitToProcessVibratorCompleteCallbacks = false;
- while (!mCompletionNotifiedVibrators.isEmpty()) {
- int vibratorId = mCompletionNotifiedVibrators.poll();
- Iterator<Step> it = mNextSteps.iterator();
- while (it.hasNext()) {
- Step step = it.next();
- if (step.acceptVibratorCompleteCallback(vibratorId)) {
- it.remove();
- mPendingOnVibratorCompleteSteps.offer(step);
- break;
- }
- }
- }
- }
- }
-
- /**
- * Represent a single step for playing a vibration.
- *
- * <p>Every step has a start time, which can be used to apply delays between steps while
- * executing them in sequence.
- */
- private abstract class Step implements Comparable<Step> {
- public final long startTime;
-
- Step(long startTime) {
- this.startTime = startTime;
- }
-
- /**
- * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
- * {@link CombinedVibration}.
- */
- public boolean isCleanUp() {
- return false;
- }
-
- /** Play this step, returning a (possibly empty) list of next steps. */
- @NonNull
- public abstract List<Step> play();
-
- /**
- * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
- * be played to gracefully cancel this step.
- */
- @NonNull
- public abstract List<Step> cancel();
-
- /** Cancel this pending step immediately, skipping any clean-up. */
- public abstract void cancelImmediately();
-
- /**
- * Return the duration the vibrator was turned on when this step was played.
- *
- * @return A positive duration that the vibrator was turned on for by this step;
- * Zero if the segment is not supported, the step was not played yet or vibrator was never
- * turned on by this step; A negative value if the vibrator call has failed.
- */
- public long getVibratorOnDuration() {
- return 0;
- }
-
- /**
- * Return true to run this step right after a vibrator has notified vibration completed,
- * used to resume steps waiting on vibrator callbacks with a timeout.
- */
- public boolean acceptVibratorCompleteCallback(int vibratorId) {
- return false;
- }
-
- /**
- * Returns the time in millis to wait before playing this step. This is performed
- * while holding the queue lock, so should not rely on potentially slow operations.
- */
- public long calculateWaitTime() {
- if (startTime == Long.MAX_VALUE) {
- // This step don't have a predefined start time, it's just marked to be executed
- // after all other steps have finished.
- return 0;
- }
- return Math.max(0, startTime - SystemClock.uptimeMillis());
- }
-
- @Override
- public int compareTo(Step o) {
- return Long.compare(startTime, o.startTime);
- }
- }
-
- /**
- * Starts a sync vibration.
- *
- * <p>If this step has successfully started playing a vibration on any vibrator, it will always
- * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
- * all their individual steps.
- *
- * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
- * sequential effect isn't finished yet.
- */
- private final class StartVibrateStep extends Step {
- public final CombinedVibration.Sequential sequentialEffect;
- public final int currentIndex;
-
- private long mVibratorsOnMaxDuration;
-
- StartVibrateStep(CombinedVibration.Sequential effect) {
- this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
- }
-
- StartVibrateStep(long startTime, CombinedVibration.Sequential effect, int index) {
- super(startTime);
- sequentialEffect = effect;
- currentIndex = index;
- }
-
- @Override
- public long getVibratorOnDuration() {
- return mVibratorsOnMaxDuration;
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
- List<Step> nextSteps = new ArrayList<>();
- mVibratorsOnMaxDuration = -1;
- try {
- if (DEBUG) {
- Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
- }
- CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
- DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
- if (effectMapping == null) {
- // Unable to map effects to vibrators, ignore this step.
- return nextSteps;
- }
-
- mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
- noteVibratorOn(mVibratorsOnMaxDuration);
- } finally {
- if (mVibratorsOnMaxDuration >= 0) {
- // It least one vibrator was started then add a finish step to wait for all
- // active vibrators to finish their individual steps before going to the next.
- // Otherwise this step was ignored so just go to the next one.
- Step nextStep =
- mVibratorsOnMaxDuration > 0 ? new FinishVibrateStep(this) : nextStep();
- if (nextStep != null) {
- nextSteps.add(nextStep);
- }
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- return nextSteps;
- }
-
- @Override
- public List<Step> cancel() {
- return EMPTY_STEP_LIST;
- }
-
- @Override
- public void cancelImmediately() {
- }
-
- /**
- * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
- * time this method is called, or null if sequence is complete.
- */
- @Nullable
- private Step nextStep() {
- int nextIndex = currentIndex + 1;
- if (nextIndex >= sequentialEffect.getEffects().size()) {
- return null;
- }
- long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
- long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
- return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
- }
-
- /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
- @Nullable
- private DeviceEffectMap createEffectToVibratorMapping(
- CombinedVibration effect) {
- if (effect instanceof CombinedVibration.Mono) {
- return new DeviceEffectMap((CombinedVibration.Mono) effect);
- }
- if (effect instanceof CombinedVibration.Stereo) {
- return new DeviceEffectMap((CombinedVibration.Stereo) effect);
- }
- return null;
- }
-
- /**
- * Starts playing effects on designated vibrators, in sync.
- *
- * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
- * @param nextSteps An output list to accumulate the future {@link Step Steps} created
- * by this method, typically one for each vibrator that has
- * successfully started vibrating on this step.
- * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
- * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
- * have ignored all effects.
- */
- private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
- int vibratorCount = effectMapping.size();
- if (vibratorCount == 0) {
- // No effect was mapped to any available vibrator.
- return 0;
- }
-
- SingleVibratorStep[] steps = new SingleVibratorStep[vibratorCount];
- long vibrationStartTime = SystemClock.uptimeMillis();
- for (int i = 0; i < vibratorCount; i++) {
- steps[i] = nextVibrateStep(vibrationStartTime,
- mVibrators.get(effectMapping.vibratorIdAt(i)),
- effectMapping.effectAt(i),
- /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
- }
-
- if (steps.length == 1) {
- // No need to prepare and trigger sync effects on a single vibrator.
- return startVibrating(steps[0], nextSteps);
- }
-
- // This synchronization of vibrators should be executed one at a time, even if we are
- // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
- // one set of vibrators at a time.
- synchronized (mLock) {
- boolean hasPrepared = false;
- boolean hasTriggered = false;
- long maxDuration = 0;
- try {
- hasPrepared = mVibratorManagerHooks.prepareSyncedVibration(
- effectMapping.getRequiredSyncCapabilities(),
- effectMapping.getVibratorIds());
-
- for (SingleVibratorStep step : steps) {
- long duration = startVibrating(step, nextSteps);
- if (duration < 0) {
- // One vibrator has failed, fail this entire sync attempt.
- return maxDuration = -1;
- }
- maxDuration = Math.max(maxDuration, duration);
- }
-
- // Check if sync was prepared and if any step was accepted by a vibrator,
- // otherwise there is nothing to trigger here.
- if (hasPrepared && maxDuration > 0) {
- hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id);
- }
- return maxDuration;
- } finally {
- if (hasPrepared && !hasTriggered) {
- // Trigger has failed or all steps were ignored by the vibrators.
- mVibratorManagerHooks.cancelSyncedVibration();
- nextSteps.clear();
- } else if (maxDuration < 0) {
- // Some vibrator failed without being prepared so other vibrators might be
- // active. Cancel and remove every pending step from output list.
- for (int i = nextSteps.size() - 1; i >= 0; i--) {
- nextSteps.remove(i).cancelImmediately();
- }
- }
- }
- }
- }
-
- private long startVibrating(SingleVibratorStep step, List<Step> nextSteps) {
- nextSteps.addAll(step.play());
- long stepDuration = step.getVibratorOnDuration();
- if (stepDuration < 0) {
- // Step failed, so return negative duration to propagate failure.
- return stepDuration;
- }
- // Return the longest estimation for the entire effect.
- return Math.max(stepDuration, step.effect.getDuration());
- }
- }
-
- /**
- * Finish a sync vibration started by a {@link StartVibrateStep}.
- *
- * <p>This only plays after all active vibrators steps have finished, and adds a {@link
- * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
- */
- private final class FinishVibrateStep extends Step {
- public final StartVibrateStep startedStep;
-
- FinishVibrateStep(StartVibrateStep startedStep) {
- super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
- this.startedStep = startedStep;
- }
-
- @Override
- public boolean isCleanUp() {
- // This step only notes that all the vibrators has been turned off.
- return true;
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
- }
- noteVibratorOff();
- Step nextStep = startedStep.nextStep();
- return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @Override
- public List<Step> cancel() {
- cancelImmediately();
- return EMPTY_STEP_LIST;
- }
-
- @Override
- public void cancelImmediately() {
- noteVibratorOff();
- }
- }
-
- /**
- * Represent a step on a single vibrator that plays one or more segments from a
- * {@link VibrationEffect.Composed} effect.
- */
- private abstract class SingleVibratorStep extends Step {
- public final VibratorController controller;
- public final VibrationEffect.Composed effect;
- public final int segmentIndex;
- public final long vibratorOffTimeout;
-
- long mVibratorOnResult;
- boolean mVibratorCompleteCallbackReceived;
-
- /**
- * @param startTime The time to schedule this step in the {@link StepQueue}.
- * @param controller The vibrator that is playing the effect.
- * @param effect The effect being played in this step.
- * @param index The index of the next segment to be played by this step
- * @param vibratorOffTimeout The time the vibrator is expected to complete any previous
- * vibration and turn off. This is used to allow this step to be
- * triggered when the completion callback is received, and can
- * be used play effects back-to-back.
- */
- SingleVibratorStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
- super(startTime);
- this.controller = controller;
- this.effect = effect;
- this.segmentIndex = index;
- this.vibratorOffTimeout = vibratorOffTimeout;
- }
-
- @Override
- public long getVibratorOnDuration() {
- return mVibratorOnResult;
- }
-
- @Override
- public boolean acceptVibratorCompleteCallback(int vibratorId) {
- boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
- mVibratorCompleteCallbackReceived |= isSameVibrator;
- // Only activate this step if a timeout was set to wait for the vibration to complete,
- // otherwise we are waiting for the correct time to play the next step.
- return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis());
- }
-
- @Override
- public List<Step> cancel() {
- return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(),
- /* cancelled= */ true, controller, vibratorOffTimeout));
- }
-
- @Override
- public void cancelImmediately() {
- if (vibratorOffTimeout > SystemClock.uptimeMillis()) {
- // Vibrator might be running from previous steps, so turn it off while canceling.
- stopVibrating();
- }
- }
-
- void stopVibrating() {
- if (DEBUG) {
- Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
- }
- controller.off();
- }
-
- void changeAmplitude(float amplitude) {
- if (DEBUG) {
- Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
- + " to " + amplitude);
- }
- controller.setAmplitude(amplitude);
- }
-
- /** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */
- public List<Step> skipToNextSteps(int segmentsSkipped) {
- return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped);
- }
-
- /**
- * Return the {@link #nextVibrateStep} with same start and off timings calculated from
- * {@link #getVibratorOnDuration()}, jumping all played segments.
- *
- * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
- * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
- */
- public List<Step> nextSteps(int segmentsPlayed) {
- if (mVibratorOnResult <= 0) {
- // Vibration was not started, so just skip the played segments and keep timings.
- return skipToNextSteps(segmentsPlayed);
- }
- long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
- long nextVibratorOffTimeout = nextStartTime + CALLBACKS_EXTRA_TIMEOUT;
- return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
- }
-
- /**
- * Return the {@link #nextVibrateStep} with given start and off timings, which might be
- * calculated independently, jumping all played segments.
- *
- * <p>This should be used when the vibrator on/off state is not responsible for the steps
- * execution timings, e.g. while playing the vibrator amplitudes.
- */
- public List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
- int segmentsPlayed) {
- Step nextStep = nextVibrateStep(nextStartTime, controller, effect,
- segmentIndex + segmentsPlayed, vibratorOffTimeout);
- return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
- }
- }
-
- /**
- * Represent a step turn the vibrator on with a single prebaked effect.
- *
- * <p>This step automatically falls back by replacing the prebaked segment with
- * {@link VibrationSettings#getFallbackEffect(int)}, if available.
- */
- private final class PerformStep extends SingleVibratorStep {
-
- PerformStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
- // This step should wait for the last vibration to finish (with the timeout) and for the
- // intended step start time (to respect the effect delays).
- super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
- vibratorOffTimeout);
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformStep");
- try {
- VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
- if (!(segment instanceof PrebakedSegment)) {
- Slog.w(TAG, "Ignoring wrong segment for a PerformStep: " + segment);
- return skipToNextSteps(/* segmentsSkipped= */ 1);
- }
-
- PrebakedSegment prebaked = (PrebakedSegment) segment;
- if (DEBUG) {
- Slog.d(TAG, "Perform " + VibrationEffect.effectIdToString(
- prebaked.getEffectId()) + " on vibrator "
- + controller.getVibratorInfo().getId());
- }
-
- VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
- mVibratorOnResult = controller.on(prebaked, mVibration.id);
-
- if (mVibratorOnResult == 0 && prebaked.shouldFallback()
- && (fallback instanceof VibrationEffect.Composed)) {
- if (DEBUG) {
- Slog.d(TAG, "Playing fallback for effect "
- + VibrationEffect.effectIdToString(prebaked.getEffectId()));
- }
- SingleVibratorStep fallbackStep = nextVibrateStep(startTime, controller,
- replaceCurrentSegment((VibrationEffect.Composed) fallback),
- segmentIndex, vibratorOffTimeout);
- List<Step> fallbackResult = fallbackStep.play();
- // Update the result with the fallback result so this step is seamlessly
- // replaced by the fallback to any outer application of this.
- mVibratorOnResult = fallbackStep.getVibratorOnDuration();
- return fallbackResult;
- }
-
- return nextSteps(/* segmentsPlayed= */ 1);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- /**
- * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
- *
- * @return a copy of {@link #effect} with replaced segment.
- */
- private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
- List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
- int newRepeatIndex = effect.getRepeatIndex();
- newSegments.remove(segmentIndex);
- newSegments.addAll(segmentIndex, fallback.getSegments());
- if (segmentIndex < effect.getRepeatIndex()) {
- newRepeatIndex += fallback.getSegments().size() - 1;
- }
- return new VibrationEffect.Composed(newSegments, newRepeatIndex);
- }
- }
-
- /**
- * Represent a step turn the vibrator on using a composition of primitives.
- *
- * <p>This step will use the maximum supported number of consecutive segments of type
- * {@link PrimitiveSegment} starting at the current index.
- */
- private final class ComposePrimitivesStep extends SingleVibratorStep {
-
- ComposePrimitivesStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
- // This step should wait for the last vibration to finish (with the timeout) and for the
- // intended step start time (to respect the effect delays).
- super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
- vibratorOffTimeout);
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
- try {
- // Load the next PrimitiveSegments to create a single compose call to the vibrator,
- // limited to the vibrator composition maximum size.
- int limit = controller.getVibratorInfo().getCompositionSizeMax();
- int segmentCount = limit > 0
- ? Math.min(effect.getSegments().size(), segmentIndex + limit)
- : effect.getSegments().size();
- List<PrimitiveSegment> primitives = new ArrayList<>();
- for (int i = segmentIndex; i < segmentCount; i++) {
- VibrationEffectSegment segment = effect.getSegments().get(i);
- if (segment instanceof PrimitiveSegment) {
- primitives.add((PrimitiveSegment) segment);
- } else {
- break;
- }
- }
-
- if (primitives.isEmpty()) {
- Slog.w(TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
- + effect.getSegments().get(segmentIndex));
- return skipToNextSteps(/* segmentsSkipped= */ 1);
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Compose " + primitives + " primitives on vibrator "
- + controller.getVibratorInfo().getId());
- }
- mVibratorOnResult = controller.on(
- primitives.toArray(new PrimitiveSegment[primitives.size()]),
- mVibration.id);
-
- return nextSteps(/* segmentsPlayed= */ primitives.size());
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
-
- /**
- * Represent a step turn the vibrator on using a composition of PWLE segments.
- *
- * <p>This step will use the maximum supported number of consecutive segments of type
- * {@link StepSegment} or {@link RampSegment} starting at the current index.
- */
- private final class ComposePwleStep extends SingleVibratorStep {
-
- ComposePwleStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
- // This step should wait for the last vibration to finish (with the timeout) and for the
- // intended step start time (to respect the effect delays).
- super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
- vibratorOffTimeout);
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
- try {
- // Load the next RampSegments to create a single composePwle call to the vibrator,
- // limited to the vibrator PWLE maximum size.
- int limit = controller.getVibratorInfo().getPwleSizeMax();
- int segmentCount = limit > 0
- ? Math.min(effect.getSegments().size(), segmentIndex + limit)
- : effect.getSegments().size();
- List<RampSegment> pwles = new ArrayList<>();
- for (int i = segmentIndex; i < segmentCount; i++) {
- VibrationEffectSegment segment = effect.getSegments().get(i);
- if (segment instanceof RampSegment) {
- pwles.add((RampSegment) segment);
- } else {
- break;
- }
- }
-
- if (pwles.isEmpty()) {
- Slog.w(TAG, "Ignoring wrong segment for a ComposePwleStep: "
- + effect.getSegments().get(segmentIndex));
- return skipToNextSteps(/* segmentsSkipped= */ 1);
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Compose " + pwles + " PWLEs on vibrator "
- + controller.getVibratorInfo().getId());
- }
- mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
- mVibration.id);
-
- return nextSteps(/* segmentsPlayed= */ pwles.size());
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
-
- /**
- * Represents a step to complete a {@link VibrationEffect}.
- *
- * <p>This runs right at the time the vibration is considered to end and will update the pending
- * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
- */
- private final class EffectCompleteStep extends SingleVibratorStep {
- private final boolean mCancelled;
-
- EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller,
- long vibratorOffTimeout) {
- super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
- mCancelled = cancelled;
- }
-
- @Override
- public boolean isCleanUp() {
- // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
- // Otherwise this step is part of the vibration.
- return mCancelled;
- }
-
- @Override
- public List<Step> cancel() {
- if (mCancelled) {
- // Double cancelling will just turn off the vibrator right away.
- return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
- }
- return super.cancel();
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
- + " step on vibrator " + controller.getVibratorInfo().getId());
- }
- if (mVibratorCompleteCallbackReceived) {
- // Vibration completion callback was received by this step, just turn if off
- // and skip any clean-up.
- stopVibrating();
- return EMPTY_STEP_LIST;
- }
-
- float currentAmplitude = controller.getCurrentAmplitude();
- long remainingOnDuration =
- vibratorOffTimeout - CALLBACKS_EXTRA_TIMEOUT - SystemClock.uptimeMillis();
- long rampDownDuration =
- Math.min(remainingOnDuration, mVibrationSettings.getRampDownDuration());
- long stepDownDuration = mVibrationSettings.getRampStepDuration();
- if (currentAmplitude < RAMP_OFF_AMPLITUDE_MIN
- || rampDownDuration <= stepDownDuration) {
- // No need to ramp down the amplitude, just wait to turn it off.
- if (mCancelled) {
- // Vibration is completing because it was cancelled, turn off right away.
- stopVibrating();
- return EMPTY_STEP_LIST;
- } else {
- return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
- }
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Ramping down vibrator " + controller.getVibratorInfo().getId()
- + " from amplitude " + currentAmplitude
- + " for " + rampDownDuration + "ms");
- }
- float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
- float amplitudeTarget = currentAmplitude - amplitudeDelta;
- long newVibratorOffTimeout = mCancelled ? rampDownDuration : vibratorOffTimeout;
- return Arrays.asList(new RampOffStep(startTime, amplitudeTarget, amplitudeDelta,
- controller, newVibratorOffTimeout));
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
-
- /** Represents a step to ramp down the vibrator amplitude before turning it off. */
- private final class RampOffStep extends SingleVibratorStep {
- private final float mAmplitudeTarget;
- private final float mAmplitudeDelta;
-
- RampOffStep(long startTime, float amplitudeTarget, float amplitudeDelta,
- VibratorController controller, long vibratorOffTimeout) {
- super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
- mAmplitudeTarget = amplitudeTarget;
- mAmplitudeDelta = amplitudeDelta;
- }
-
- @Override
- public boolean isCleanUp() {
- return true;
- }
-
- @Override
- public List<Step> cancel() {
- return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffStep");
- try {
- if (DEBUG) {
- long latency = SystemClock.uptimeMillis() - startTime;
- Slog.d(TAG, "Ramp down the vibrator amplitude, step with "
- + latency + "ms latency.");
- }
- if (mVibratorCompleteCallbackReceived) {
- // Vibration completion callback was received by this step, just turn if off
- // and skip the rest of the steps to ramp down the vibrator amplitude.
- stopVibrating();
- return EMPTY_STEP_LIST;
- }
-
- changeAmplitude(mAmplitudeTarget);
-
- float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
- if (newAmplitudeTarget < RAMP_OFF_AMPLITUDE_MIN) {
- // Vibrator amplitude cannot go further down, just turn it off.
- return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
- }
- return Arrays.asList(new RampOffStep(
- startTime + mVibrationSettings.getRampStepDuration(), newAmplitudeTarget,
- mAmplitudeDelta, controller, vibratorOffTimeout));
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
-
- /**
- * Represents a step to turn the vibrator off.
- *
- * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
- * and can be brought forward by vibrator complete callbacks.
- */
- private final class OffStep extends SingleVibratorStep {
-
- OffStep(long startTime, VibratorController controller) {
- super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
- }
-
- @Override
- public boolean isCleanUp() {
- return true;
- }
-
- @Override
- public List<Step> cancel() {
- return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
- }
-
- @Override
- public void cancelImmediately() {
- stopVibrating();
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep");
- try {
- stopVibrating();
- return EMPTY_STEP_LIST;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
-
- /**
- * Represents a step to turn the vibrator on and change its amplitude.
- *
- * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
- * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
- */
- private final class AmplitudeStep extends SingleVibratorStep {
- private long mNextOffTime;
-
- AmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
- // This step has a fixed startTime coming from the timings of the waveform it's playing.
- super(startTime, controller, effect, index, vibratorOffTimeout);
- mNextOffTime = vibratorOffTimeout;
- }
-
- @Override
- public boolean acceptVibratorCompleteCallback(int vibratorId) {
- if (controller.getVibratorInfo().getId() == vibratorId) {
- mVibratorCompleteCallbackReceived = true;
- mNextOffTime = SystemClock.uptimeMillis();
- }
- // Timings are tightly controlled here, so only trigger this step if the vibrator was
- // supposed to be ON but has completed prematurely, to turn it back on as soon as
- // possible.
- return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
- }
-
- @Override
- public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
- try {
- long now = SystemClock.uptimeMillis();
- long latency = now - startTime;
- if (DEBUG) {
- Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
- }
-
- if (mVibratorCompleteCallbackReceived && latency < 0) {
- // This step was run early because the vibrator turned off prematurely.
- // Turn it back on and return this same step to run at the exact right time.
- mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
- return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
- segmentIndex, mNextOffTime));
- }
-
- VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
- if (!(segment instanceof StepSegment)) {
- Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
- return skipToNextSteps(/* segmentsSkipped= */ 1);
- }
-
- StepSegment stepSegment = (StepSegment) segment;
- if (stepSegment.getDuration() == 0) {
- // Skip waveform entries with zero timing.
- return skipToNextSteps(/* segmentsSkipped= */ 1);
- }
-
- float amplitude = stepSegment.getAmplitude();
- if (amplitude == 0) {
- if (vibratorOffTimeout > now) {
- // Amplitude cannot be set to zero, so stop the vibrator.
- stopVibrating();
- mNextOffTime = now;
- }
- } else {
- if (startTime >= mNextOffTime) {
- // Vibrator is OFF. Turn vibrator back on for the duration of another
- // cycle before setting the amplitude.
- long onDuration = getVibratorOnDuration(effect, segmentIndex);
- if (onDuration > 0) {
- mVibratorOnResult = startVibrating(onDuration);
- mNextOffTime = now + onDuration + CALLBACKS_EXTRA_TIMEOUT;
- }
- }
- changeAmplitude(amplitude);
- }
-
- // Use original startTime to avoid propagating latencies to the waveform.
- long nextStartTime = startTime + segment.getDuration();
- return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private long turnVibratorBackOn(long remainingDuration) {
- long onDuration = getVibratorOnDuration(effect, segmentIndex);
- if (onDuration <= 0) {
- // Vibrator is supposed to go back off when this step starts, so just leave it off.
- return vibratorOffTimeout;
- }
- onDuration += remainingDuration;
- float expectedAmplitude = controller.getCurrentAmplitude();
- mVibratorOnResult = startVibrating(onDuration);
- if (mVibratorOnResult > 0) {
- // Set the amplitude back to the value it was supposed to be playing at.
- changeAmplitude(expectedAmplitude);
- }
- return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
- }
-
- private long startVibrating(long duration) {
- if (DEBUG) {
- Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
- + duration + "ms");
- }
- return controller.on(duration, mVibration.id);
- }
-
- /**
- * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
- * until the next time it's vibrating amplitude is zero or a different type of segment is
- * found.
- */
- private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
- List<VibrationEffectSegment> segments = effect.getSegments();
- int segmentCount = segments.size();
- int repeatIndex = effect.getRepeatIndex();
- int i = startIndex;
- long timing = 0;
- while (i < segmentCount) {
- VibrationEffectSegment segment = segments.get(i);
- if (!(segment instanceof StepSegment)
- || ((StepSegment) segment).getAmplitude() == 0) {
- break;
- }
- timing += segment.getDuration();
- i++;
- if (i == segmentCount && repeatIndex >= 0) {
- i = repeatIndex;
- // prevent infinite loop
- repeatIndex = -1;
- }
- if (i == startIndex) {
- // The repeating waveform keeps the vibrator ON all the time. Use a minimum
- // of 1s duration to prevent short patterns from turning the vibrator ON too
- // frequently.
- return Math.max(timing, 1000);
- }
- }
- if (i == segmentCount && effect.getRepeatIndex() < 0) {
- // Vibration ending at non-zero amplitude, add extra timings to ramp down after
- // vibration is complete.
- timing += mVibrationSettings.getRampDownDuration();
- }
- return timing;
- }
- }
-
- /**
- * Map a {@link CombinedVibration} to the vibrators available on the device.
- *
- * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
- * play all of the effects in sync.
- */
- private final class DeviceEffectMap {
- private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
- private final int[] mVibratorIds;
- private final long mRequiredSyncCapabilities;
-
- DeviceEffectMap(CombinedVibration.Mono mono) {
- mVibratorEffects = new SparseArray<>(mVibrators.size());
- mVibratorIds = new int[mVibrators.size()];
- for (int i = 0; i < mVibrators.size(); i++) {
- int vibratorId = mVibrators.keyAt(i);
- VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
- VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo);
- if (effect instanceof VibrationEffect.Composed) {
- mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
- mVibratorIds[i] = vibratorId;
- }
- }
- mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
- }
-
- DeviceEffectMap(CombinedVibration.Stereo stereo) {
- SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
- mVibratorEffects = new SparseArray<>();
- for (int i = 0; i < stereoEffects.size(); i++) {
- int vibratorId = stereoEffects.keyAt(i);
- if (mVibrators.contains(vibratorId)) {
- VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
- VibrationEffect effect = mDeviceEffectAdapter.apply(
- stereoEffects.valueAt(i), vibratorInfo);
- if (effect instanceof VibrationEffect.Composed) {
- mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
- }
- }
- }
- mVibratorIds = new int[mVibratorEffects.size()];
- for (int i = 0; i < mVibratorEffects.size(); i++) {
- mVibratorIds[i] = mVibratorEffects.keyAt(i);
- }
- mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
- }
-
- /**
- * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
- * device.
- */
- public int size() {
- return mVibratorIds.length;
- }
-
- /**
- * Return all capabilities required to play the {@link CombinedVibration} in
- * between calls to {@link IVibratorManager#prepareSynced} and
- * {@link IVibratorManager#triggerSynced}.
- */
- public long getRequiredSyncCapabilities() {
- return mRequiredSyncCapabilities;
- }
-
- /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
- public int[] getVibratorIds() {
- return mVibratorIds;
- }
-
- /** Return the id of the vibrator at given index. */
- public int vibratorIdAt(int index) {
- return mVibratorEffects.keyAt(index);
- }
-
- /** Return the {@link VibrationEffect} at given index. */
- public VibrationEffect.Composed effectAt(int index) {
- return mVibratorEffects.valueAt(index);
- }
-
- /**
- * Return all capabilities required from the {@link IVibratorManager} to prepare and
- * trigger all given effects in sync.
- *
- * @return {@link IVibratorManager#CAP_SYNC} together with all required
- * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
- */
- private long calculateRequiredSyncCapabilities(
- SparseArray<VibrationEffect.Composed> effects) {
- long prepareCap = 0;
- for (int i = 0; i < effects.size(); i++) {
- VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
- if (firstSegment instanceof StepSegment) {
- prepareCap |= IVibratorManager.CAP_PREPARE_ON;
- } else if (firstSegment instanceof PrebakedSegment) {
- prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
- } else if (firstSegment instanceof PrimitiveSegment) {
- prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
- }
- }
- int triggerCap = 0;
- if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
- triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
- }
- if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
- triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
- }
- if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
- triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
- }
- return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
- }
-
- /**
- * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
- * different ones, requiring a mixed trigger capability from the vibrator manager for
- * syncing all effects.
- */
- private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
- return (prepareCapabilities & capability) != 0
- && (prepareCapabilities & ~capability) != 0;
- }
- }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 63f3af3f3095..01f9d0b94879 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -42,6 +42,7 @@ import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
@@ -60,6 +61,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -88,6 +90,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
| VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+ private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
/** Lifecycle responsible for initializing this class at the right system server phases. */
public static class Lifecycle extends SystemService {
private VibratorManagerService mService;
@@ -201,8 +206,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
.getSystemUiServiceComponent().getPackageName();
- mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
- BatteryStats.SERVICE_NAME));
+ mBatteryStatsService = injector.getBatteryStatsService();
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -634,7 +638,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
- mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
+ mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
mVibrationThreadCallbacks);
if (mCurrentVibration == null) {
@@ -1105,6 +1109,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return new Handler(looper);
}
+ IBatteryStats getBatteryStatsService() {
+ return IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+ }
+
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return new VibratorController(vibratorId, listener);
@@ -1141,6 +1150,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
+ public void noteVibratorOn(int uid, long duration) {
+ try {
+ if (duration <= 0) {
+ return;
+ }
+ if (duration == Long.MAX_VALUE) {
+ // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+ // should be called when this is cancelled.
+ duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
+ }
+ mBatteryStatsService.noteVibratorOn(uid, duration);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+ duration);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void noteVibratorOff(int uid) {
+ try {
+ mBatteryStatsService.noteVibratorOff(uid);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+ /* duration= */ 0);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ffa1a601d41b..98c74f8ad81d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5149,10 +5149,13 @@ class Task extends TaskFragment {
// Ensure that we do not trigger entering PiP an activity on the root pinned task
return false;
}
+ final boolean isTransient = opts != null && opts.getTransientLaunch();
final Task targetRootTask = toFrontTask != null
? toFrontTask.getRootTask() : toFrontActivity.getRootTask();
- if (targetRootTask != null && targetRootTask.isActivityTypeAssistant()) {
- // Ensure the task/activity being brought forward is not the assistant
+ if (targetRootTask != null && (targetRootTask.isActivityTypeAssistant() || isTransient)) {
+ // Ensure the task/activity being brought forward is not the assistant and is not
+ // transient. In the case of transient-launch, we want to wait until the end of the
+ // transition and only allow switch if the transient launch was committed.
return false;
}
return true;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index a9add59be1f6..6a23eb588102 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -310,7 +310,7 @@ class TaskSnapshotController {
}
final ActivityRecord activity = result.first;
final WindowState mainWindow = result.second;
- final Rect contentInsets = getSystemBarInsets(task.getBounds(),
+ final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
mainWindow.getInsetsStateWithVisibilityOverride());
final Rect letterboxInsets = activity.getLetterboxInsets();
InsetUtils.addInsets(contentInsets, letterboxInsets);
@@ -575,7 +575,7 @@ class TaskSnapshotController {
final LayoutParams attrs = mainWindow.getAttrs();
final Rect taskBounds = task.getBounds();
final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
- final Rect systemBarInsets = getSystemBarInsets(taskBounds, insetsState);
+ final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
mHighResTaskSnapshotScale, insetsState);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0b965c37a2ba..3a3103e752ad 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -459,9 +459,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// activity in a bad state.
if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
boolean commitVisibility = true;
- if (ar.getDeferHidingClient() && ar.getTask() != null) {
+ if (ar.isVisible() && ar.getTask() != null) {
if (ar.pictureInPictureArgs != null
&& ar.pictureInPictureArgs.isAutoEnterEnabled()) {
+ if (mTransientLaunches != null) {
+ for (int j = 0; j < mTransientLaunches.size(); ++j) {
+ if (mTransientLaunches.valueAt(j).isVisibleRequested()) {
+ // force enable pip-on-task-switch now that we've committed
+ // to actually launching to the transient activity.
+ ar.supportsEnterPipOnTaskSwitch = true;
+ break;
+ }
+ }
+ }
mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
// Avoid commit visibility to false here, or else we will get a sudden
// "flash" / surface going invisible for a split second.
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index cd2d0fce6691..83ccabf03935 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -238,6 +238,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::isVendor,
AndroidPackage::isVisibleToInstantApps,
AndroidPackage::isVmSafeMode,
+ AndroidPackage::isLeavingSharedUid,
AndroidPackage::isResetEnabledSettingsOnAppDataCleared,
AndroidPackage::getMaxAspectRatio,
AndroidPackage::getMinAspectRatio,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index a9b7cfb5e338..3ce2ed84d3e8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -52,7 +52,6 @@ import android.view.accessibility.MagnificationAnimationCallback;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
@@ -94,7 +93,6 @@ public class FullScreenMagnificationControllerTest {
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
- final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
@@ -121,12 +119,10 @@ public class FullScreenMagnificationControllerTest {
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
- when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
- when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
initMockWindowManager();
mFullScreenMagnificationController = new FullScreenMagnificationController(
@@ -357,8 +353,8 @@ public class FullScreenMagnificationControllerTest {
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
- verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
- mConfigCaptor.capture());
+ verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+ eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
assertConfigEquals(config, mConfigCaptor.getValue());
verify(mMockValueAnimator).start();
verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1);
@@ -501,7 +497,7 @@ public class FullScreenMagnificationControllerTest {
mMessageCapturingHandler.sendAllMessages();
MagnificationConfig config = buildConfig(1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(),
OTHER_MAGNIFICATION_BOUNDS.centerY());
- verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(OTHER_REGION),
+ verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), eq(OTHER_REGION),
mConfigCaptor.capture());
assertConfigEquals(config, mConfigCaptor.getValue());
}
@@ -655,9 +651,9 @@ public class FullScreenMagnificationControllerTest {
register(displayId);
zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- reset(mMockAms);
+ reset(mRequestObserver);
assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
- verify(mMockAms).notifyMagnificationChanged(eq(displayId),
+ verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class));
assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
@@ -676,8 +672,8 @@ public class FullScreenMagnificationControllerTest {
assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback));
mMessageCapturingHandler.sendAllMessages();
- verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class),
- any(MagnificationConfig.class));
+ verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
+ any(Region.class), any(MagnificationConfig.class));
verify(mAnimationCallback).onResult(true);
}
@@ -1072,8 +1068,8 @@ public class FullScreenMagnificationControllerTest {
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
- verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
- mConfigCaptor.capture());
+ verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+ eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
assertConfigEquals(config, mConfigCaptor.getValue());
Mockito.reset(mMockWindowManager);
@@ -1097,7 +1093,7 @@ public class FullScreenMagnificationControllerTest {
// Animation should have been restarted
verify(mMockValueAnimator, times(2)).start();
- verify(mMockAms, times(2)).notifyMagnificationChanged(eq(displayId),
+ verify(mRequestObserver, times(2)).onFullScreenMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
assertConfigEquals(newConfig, mConfigCaptor.getValue());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2060223f6f98..0fed89b831d6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -151,8 +151,6 @@ public class FullScreenMagnificationGestureHandlerTest {
mock(FullScreenMagnificationController.ControllerContext.class);
final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
when(mockController.getContext()).thenReturn(mContext);
- when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService);
- when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager);
when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
when(mockController.getWindowManager()).thenReturn(mockWindowManager);
when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 3fcce92fe141..ec59090240f3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -145,9 +145,10 @@ public class MagnificationControllerTest {
mCallbackDelegate, mTraceManager, mScaleProvider));
mMockConnection = new MockWindowMagnificationConnection(true);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
- new FullScreenMagnificationControllerStubber(mScreenMagnificationController);
mMagnificationController = new MagnificationController(mService, globalLock, mContext,
mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider);
+ new FullScreenMagnificationControllerStubber(mScreenMagnificationController,
+ mMagnificationController);
mMagnificationController.setMagnificationCapabilities(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -451,6 +452,29 @@ public class MagnificationControllerTest {
}
@Test
+ public void onFullScreenMagnificationChanged_fullScreenEnabled_notifyMagnificationChanged()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+
+ final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
+ mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
+ config.getScale(), config.getCenterX(), config.getCenterY(),
+ true, TEST_SERVICE_ID);
+
+ // The first time is triggered when setting magnification enabled. And the second time is
+ // triggered when calling setScaleAndCenter.
+ final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+ MagnificationConfig.class);
+ verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY),
+ eq(FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION),
+ configCaptor.capture());
+ final MagnificationConfig actualConfig = configCaptor.getValue();
+ assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
+ assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
+ assertEquals(config.getScale(), actualConfig.getScale(), 0);
+ }
+
+ @Test
public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton()
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
@@ -679,7 +703,7 @@ public class MagnificationControllerTest {
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
- /* scale= */1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
+ /* scale= */ 1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
true, TEST_SERVICE_ID);
mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
@@ -884,6 +908,8 @@ public class MagnificationControllerTest {
private static class FullScreenMagnificationControllerStubber {
private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600);
private final FullScreenMagnificationController mScreenMagnificationController;
+ private final FullScreenMagnificationController.MagnificationInfoChangedCallback
+ mMagnificationChangedCallback;
private boolean mIsMagnifying = false;
private float mScale = 1.0f;
private float mCenterX = MAGNIFICATION_REGION.getBounds().exactCenterX();
@@ -891,8 +917,10 @@ public class MagnificationControllerTest {
private int mServiceId = -1;
FullScreenMagnificationControllerStubber(
- FullScreenMagnificationController screenMagnificationController) {
+ FullScreenMagnificationController screenMagnificationController,
+ FullScreenMagnificationController.MagnificationInfoChangedCallback callback) {
mScreenMagnificationController = screenMagnificationController;
+ mMagnificationChangedCallback = callback;
stubMethods();
}
@@ -930,6 +958,14 @@ public class MagnificationControllerTest {
} else {
reset();
}
+
+
+ final MagnificationConfig config = new MagnificationConfig.Builder().setMode(
+ MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY(
+ mCenterY).build();
+ mMagnificationChangedCallback.onFullScreenMagnificationChanged(TEST_DISPLAY,
+ FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION,
+ config);
return true;
};
doAnswer(setScaleAndCenterStubAnswer).when(
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
new file mode 100644
index 000000000000..b154d6f6db0c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.server.backup.transport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.backup.ITransportStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupTransportClientTest {
+
+ private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
+ public final Object mLock = new Object();
+
+ public String mNameCompletedImmediately;
+
+ @GuardedBy("mLock")
+ public AndroidFuture<String> mNameCompletedInFuture;
+
+ @Override public void name(AndroidFuture<String> name) throws RemoteException {
+ name.complete(mNameCompletedImmediately);
+ }
+ @Override public void transportDirName(AndroidFuture<String> name) throws RemoteException {
+ synchronized (mLock) {
+ mNameCompletedInFuture = name;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ @Test
+ public void testName_completesImmediately_returnsName() throws Exception {
+ TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+ binder.mNameCompletedImmediately = "fake name";
+
+ BackupTransportClient client = new BackupTransportClient(binder);
+ String name = client.name();
+
+ assertThat(name).isEqualTo("fake name");
+ }
+
+ @Test
+ public void testTransportDirName_completesLater_returnsName() throws Exception {
+ TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+ BackupTransportClient client = new BackupTransportClient(binder);
+
+ Thread thread = new Thread(() -> {
+ try {
+ String name = client.transportDirName();
+ assertThat(name).isEqualTo("fake name");
+ } catch (Exception ex) {
+ fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+ }
+ });
+
+ thread.start();
+
+ synchronized (binder.mLock) {
+ while (binder.mNameCompletedInFuture == null) {
+ binder.mLock.wait();
+ }
+ assertThat(binder.mNameCompletedInFuture.complete("fake name")).isTrue();
+ }
+
+ thread.join();
+ }
+
+ @Test
+ public void testTransportDirName_canceledBeforeCompletion_throwsException() throws Exception {
+ TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+ BackupTransportClient client = new BackupTransportClient(binder);
+
+ Thread thread = new Thread(() -> {
+ try {
+ /*String name =*/ client.transportDirName();
+ fail("transportDirName should be cancelled");
+ } catch (CancellationException ex) {
+ // This is expected.
+ } catch (Exception ex) {
+ fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+ }
+ });
+
+ thread.start();
+
+ synchronized (binder.mLock) {
+ while (binder.mNameCompletedInFuture == null) {
+ binder.mLock.wait();
+ }
+ client.onBecomingUnusable();
+ }
+
+ thread.join();
+ }
+
+ private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
+ public final Object mLock = new Object();
+
+ public int mStatusCompletedImmediately;
+
+ @GuardedBy("mLock")
+ public ITransportStatusCallback mStatusCompletedInFuture;
+
+ @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException {
+ c.onOperationCompleteWithStatus(mStatusCompletedImmediately);
+ }
+ @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException {
+ synchronized (mLock) {
+ mStatusCompletedInFuture = c;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ @Test
+ public void testInitializeDevice_completesImmediately_returnsStatus() throws Exception {
+ TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+ binder.mStatusCompletedImmediately = 123;
+
+ BackupTransportClient client = new BackupTransportClient(binder);
+ int status = client.initializeDevice();
+
+ assertThat(status).isEqualTo(123);
+ }
+
+
+ @Test
+ public void testFinishBackup_completesLater_returnsStatus() throws Exception {
+ TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+ BackupTransportClient client = new BackupTransportClient(binder);
+
+ Thread thread = new Thread(() -> {
+ try {
+ int status = client.finishBackup();
+ assertThat(status).isEqualTo(456);
+ } catch (Exception ex) {
+ fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+ }
+ });
+
+ thread.start();
+
+ synchronized (binder.mLock) {
+ while (binder.mStatusCompletedInFuture == null) {
+ binder.mLock.wait();
+ }
+ binder.mStatusCompletedInFuture.onOperationCompleteWithStatus(456);
+ }
+
+ thread.join();
+ }
+
+ @Test
+ public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception {
+ TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+ BackupTransportClient client = new BackupTransportClient(binder);
+
+ Thread thread = new Thread(() -> {
+ try {
+ int status = client.finishBackup();
+ assertThat(status).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ } catch (Exception ex) {
+ fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+ }
+ });
+
+ thread.start();
+
+ synchronized (binder.mLock) {
+ while (binder.mStatusCompletedInFuture == null) {
+ binder.mLock.wait();
+ }
+ client.onBecomingUnusable();
+ }
+
+ thread.join();
+ }
+
+ // Convenience layer so we only need to fake specific methods useful for each test case.
+ private static class FakeTransportBinderBase implements IBackupTransport {
+ @Override public void name(AndroidFuture<String> f) throws RemoteException {}
+ @Override public void transportDirName(AndroidFuture<String> f) throws RemoteException {}
+ @Override public void configurationIntent(AndroidFuture<Intent> f) throws RemoteException {}
+ @Override public void currentDestinationString(AndroidFuture<String> f)
+ throws RemoteException {}
+ @Override public void dataManagementIntent(AndroidFuture<Intent> f)
+ throws RemoteException {}
+ @Override public void dataManagementIntentLabel(AndroidFuture<CharSequence> f)
+ throws RemoteException {}
+ @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException {}
+ @Override public void clearBackupData(PackageInfo i, ITransportStatusCallback c)
+ throws RemoteException {}
+ @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException {}
+ @Override public void requestBackupTime(AndroidFuture<Long> f) throws RemoteException {}
+ @Override public void performBackup(PackageInfo i, ParcelFileDescriptor fd, int f,
+ ITransportStatusCallback c) throws RemoteException {}
+ @Override public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> f)
+ throws RemoteException {}
+ @Override public void getCurrentRestoreSet(AndroidFuture<Long> f) throws RemoteException {}
+ @Override public void startRestore(long t, PackageInfo[] p, ITransportStatusCallback c)
+ throws RemoteException {}
+ @Override public void nextRestorePackage(AndroidFuture<RestoreDescription> f)
+ throws RemoteException {}
+ @Override public void getRestoreData(ParcelFileDescriptor fd, ITransportStatusCallback c)
+ throws RemoteException {}
+ @Override public void finishRestore(ITransportStatusCallback c) throws RemoteException {}
+ @Override public void requestFullBackupTime(AndroidFuture<Long> f) throws RemoteException {}
+ @Override public void performFullBackup(PackageInfo i, ParcelFileDescriptor fd, int f,
+ ITransportStatusCallback c) throws RemoteException {}
+ @Override public void checkFullBackupSize(long s, ITransportStatusCallback c)
+ throws RemoteException {}
+ @Override public void sendBackupData(int n, ITransportStatusCallback c)
+ throws RemoteException {}
+ @Override public void cancelFullBackup(ITransportStatusCallback c) throws RemoteException {}
+ @Override public void isAppEligibleForBackup(PackageInfo p, boolean b,
+ AndroidFuture<Boolean> f) throws RemoteException {}
+ @Override public void getBackupQuota(String s, boolean b, AndroidFuture<Long> f)
+ throws RemoteException {}
+ @Override public void getNextFullRestoreDataChunk(ParcelFileDescriptor fd,
+ ITransportStatusCallback c) throws RemoteException {}
+ @Override public void abortFullRestore(ITransportStatusCallback c) throws RemoteException {}
+ @Override public void getTransportFlags(AndroidFuture<Integer> f) throws RemoteException {}
+ @Override public IBinder asBinder() {
+ return null;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index e1a4989e5a05..01e306e744fb 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -61,8 +61,6 @@ import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
-import com.android.internal.app.IBatteryStats;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -106,8 +104,6 @@ public class VibrationThreadTest {
@Mock
private IBinder mVibrationToken;
@Mock
- private IBatteryStats mIBatteryStatsMock;
- @Mock
private VibrationConfig mVibrationConfigMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -178,8 +174,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -197,8 +193,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -219,8 +215,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -252,8 +248,8 @@ public class VibrationThreadTest {
thread.cancel();
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -404,8 +400,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -427,8 +423,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibration);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -446,8 +442,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
- verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+ verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -466,8 +462,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -486,8 +482,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
- verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+ verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -536,8 +532,8 @@ public class VibrationThreadTest {
waitForCompletion(thread);
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -573,8 +569,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -666,8 +662,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
@@ -690,8 +686,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -728,8 +724,8 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -777,13 +773,13 @@ public class VibrationThreadTest {
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
- InOrder batterVerifier = inOrder(mIBatteryStatsMock);
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
- batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ InOrder batteryVerifier = inOrder(mManagerHooks);
+ batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+ batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+ batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+ batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+ batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(1).isVibrating());
@@ -952,8 +948,8 @@ public class VibrationThreadTest {
waitForCompletion(thread);
- verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(80L));
- verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -1300,7 +1296,7 @@ public class VibrationThreadTest {
private VibrationThread startThreadAndDispatcher(Vibration vib) {
VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
- createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks);
+ createVibratorControllers(), mWakeLock, mManagerHooks);
doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0));
return null;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ccdb1050d32c..19111e5d16e9 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -71,6 +71,7 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -78,6 +79,7 @@ import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.IBatteryStats;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -144,6 +146,8 @@ public class VibratorManagerServiceTest {
private AppOpsManager mAppOpsManagerMock;
@Mock
private IInputManager mIInputManagerMock;
+ @Mock
+ private IBatteryStats mBatteryStatsMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -152,12 +156,14 @@ public class VibratorManagerServiceTest {
private FakeVibrator mVibrator;
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+ private VibrationConfig mVibrationConfig;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+ mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -222,6 +228,11 @@ public class VibratorManagerServiceTest {
}
@Override
+ IBatteryStats getBatteryStatsService() {
+ return mBatteryStatsMock;
+ }
+
+ @Override
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return mVibratorProviders.get(vibratorId)
@@ -382,6 +393,11 @@ public class VibratorManagerServiceTest {
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
inOrderVerifier.verifyNoMoreInteractions();
+
+ InOrder batteryVerifier = inOrder(mBatteryStatsMock);
+ batteryVerifier.verify(mBatteryStatsMock)
+ .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
+ batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
}
@Test
@@ -731,6 +747,12 @@ public class VibratorManagerServiceTest {
// Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1,
service, /* timeout= */ 50));
+
+ // The time estimate is recorded when the vibration starts, repeating vibrations
+ // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
+ verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
+ // The second vibration shouldn't have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
}
@Test
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f57c32c959d9..0394a546388d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4189,7 +4189,8 @@ public class TelephonyManager {
* {@link UiccSlotMapping} which consist of both physical slot index and port index.
* Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot.
* Port index is the index (enumerated value) for the associated port available on the SIM.
- * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported.
+ * Each physical slot can have multiple ports if
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} is supported.
*
* Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot
* has one port:
@@ -4285,11 +4286,11 @@ public class TelephonyManager {
/**
* Get the mapping from logical slots to physical sim slots and port indexes. Initially the
* logical slot index was mapped to physical slot index, but with support for multi-enabled
- * profile(MEP) logical slot is now mapped to port index.
+ * profile(MEP){@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},logical slot is now mapped to
+ * port index.
*
* @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
* slots to ports and physical slots.
- *
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 30ca1627953f..3843a6240b43 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -17,6 +17,7 @@ package android.telephony;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.PackageManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -147,9 +148,10 @@ public final class UiccCardInfo implements Parcelable {
* Note that this field may be omitted if the caller does not have the correct permissions
* (see {@link TelephonyManager#getUiccCardsInfo()}).
*
- * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one
- * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
- * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}
+ * @deprecated with support for MEP(multiple enabled profile)
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, a SIM card can have more than one
+ * ICCID active at the same time. Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
+ * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}.
*
* @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
*/
@@ -192,11 +194,11 @@ public final class UiccCardInfo implements Parcelable {
}
/*
- * Whether the UICC card supports multiple enable profile(MEP)
+ * Whether the UICC card supports multiple enabled profile(MEP)
* UICCs are generally MEP disabled, there can be only one active profile on the physical
* sim card.
*
- * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP).
+ * @return {@code true} if the UICC is supporting multiple enabled profile(MEP).
*/
public boolean isMultipleEnabledProfilesSupported() {
return mIsMultipleEnabledProfilesSupported;
@@ -205,6 +207,9 @@ public final class UiccCardInfo implements Parcelable {
/**
* Get information regarding port, ICCID and its active status.
*
+ * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+ * more than one {@link UiccPortInfo} object if the card is eUICC.
+ *
* @return Collection of {@link UiccPortInfo}
*/
public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java
index d1838c0b91f4..6fb0470d6225 100644
--- a/telephony/java/android/telephony/UiccPortInfo.java
+++ b/telephony/java/android/telephony/UiccPortInfo.java
@@ -29,7 +29,9 @@ import java.util.Objects;
* Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on
* a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1.
* Each port index is unique within an UICC, but not necessarily unique across UICC’s.
- * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0.
+ * For UICC's does not support MEP(Multi-enabled profile)
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, just return the default
+ * port index 0.
*/
public final class UiccPortInfo implements Parcelable{
private final String mIccId;
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 17f34db4e44a..17ce45063d41 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -19,6 +19,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -225,6 +226,9 @@ public class UiccSlotInfo implements Parcelable {
/**
* Get Information regarding port, iccid and its active status.
*
+ * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+ * more than one {@link UiccPortInfo} object if the card is eUICC.
+ *
* @return Collection of {@link UiccPortInfo}
*/
public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index b6ae53017f64..4820d332de0f 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
import android.telephony.euicc.EuiccCardManager.ResetOption;
import android.util.Log;
@@ -931,6 +932,21 @@ public class EuiccManager {
* intent to prompt the user to accept the download. The caller should also be authorized to
* manage the subscription to be downloaded.
*
+ * <p>If device support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} and
+ * switchAfterDownload is {@code true}, the subscription will be enabled on an esim port based
+ * on the following selection rules:
+ * <ul>
+ * <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then download
+ * and enable the subscription on this port.
+ * <li>In SS mode, if the embedded slot is not active, then try to download and enable the
+ * subscription on the default port 0 of eUICC.
+ * <li>In DSDS mode, find first available port to download and enable the subscription.
+ * (see {@link #isSimPortAvailable(int)})
+ *</ul>
+ * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+ * will be returned in the callback intent to prompt the user to disable an already-active
+ * subscription.
+ *
* @param subscription the subscription to download.
* @param switchAfterDownload if true, the profile will be activated upon successful download.
* @param callbackIntent a PendingIntent to launch when the operation completes.
@@ -1141,14 +1157,25 @@ public class EuiccManager {
* intent to prompt the user to accept the download. The caller should also be authorized to
* manage the subscription to be enabled.
*
- * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription
- * can be installed on different port from the eUICC. Calling apps with carrier privilege
- * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions
- * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to
- * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC
- * and the platform will internally resolve a port. If there is no available port,
- * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback
- * intent to prompt the user to disable an already-active subscription.
+ * <p> From Android T, devices might support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},
+ * the subscription can be installed on different port from the eUICC. Calling apps with
+ * carrier privilege (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently
+ * active subscriptions can use {@link #switchToSubscription(int, int, PendingIntent)} to
+ * specify which port to enable the subscription. Otherwise, use this API to enable the
+ * subscription on the eUICC and the platform will internally resolve a port based on following
+ * rules:
+ * <ul>
+ * <li>always use the default port 0 is eUICC does not support MEP.
+ * <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then enable
+ * the subscription on this port.
+ * <li>In SS mode, if the embedded slot is not active, then try to enable the subscription on
+ * the default port 0 of eUICC.
+ * <li>In DSDS mode, find first available port to enable the subscription.
+ * (see {@link #isSimPortAvailable(int)})
+ *</ul>
+ * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+ * will be returned in the callback intent to prompt the user to disable an already-active
+ * subscription.
*
* @param subscriptionId the ID of the subscription to enable. May be
* {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
@@ -1197,7 +1224,15 @@ public class EuiccManager {
*
* <p> If the caller is passing invalid port index,
* an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code
- * {@link #ERROR_INVALID_PORT} will be returned.
+ * {@link #ERROR_INVALID_PORT} will be returned. The port index is invalid if one of the
+ * following requirements is met:
+ * <ul>
+ * <li>index is beyond the range of {@link UiccCardInfo#getPorts()}.
+ * <li>In SS(Single SIM) mode, the embedded slot already has an active port with different
+ * port index.
+ * <li>In DSDS mode, if the psim slot is active and the embedded slot already has an active
+ * empty port with different port index.
+ * </ul>
*
* <p> Depending on the target port and permission check,
* an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback
@@ -1522,8 +1557,8 @@ public class EuiccManager {
/**
* Returns whether the passing portIndex is available.
- * A port is available if it has no profiles enabled on it or calling app has carrier privilege
- * over the profile installed on the selected port.
+ * A port is available if it is active without enabled profile on it or
+ * calling app has carrier privilege over the profile installed on the selected port.
* Always returns false if the cardId is a physical card.
*
* @param portIndex is an enumeration of the ports available on the UICC.