Merge "Move TracingTests to presubmit" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index cc0354c..569776e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13020,6 +13020,8 @@
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String EXTRA_NOTIFICATION_KEY = "android.service.notification.extra.NOTIFICATION_KEY";
field public static final String FEEDBACK_RATING = "feedback.rating";
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
field public static final int SOURCE_FROM_APP = 0; // 0x0
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index fad289c..edcdb6c 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -1047,7 +1047,7 @@
private static String startComponentToString(@StartComponent int startComponent) {
return switch (startComponent) {
case START_COMPONENT_ACTIVITY -> "ACTIVITY";
- case START_COMPONENT_BROADCAST -> "SERVICE";
+ case START_COMPONENT_BROADCAST -> "BROADCAST";
case START_COMPONENT_CONTENT_PROVIDER -> "CONTENT PROVIDER";
case START_COMPONENT_SERVICE -> "SERVICE";
case START_COMPONENT_OTHER -> "OTHER";
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
index 8d25cad..4598421 100644
--- a/core/java/android/app/supervision/ISupervisionManager.aidl
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -21,5 +21,5 @@
* {@hide}
*/
interface ISupervisionManager {
- boolean isSupervisionEnabled();
+ boolean isSupervisionEnabledForUser(int userId);
}
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 8611a92..aee1cd9 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -17,6 +17,7 @@
package android.app.supervision;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.RemoteException;
@@ -45,13 +46,12 @@
*
* @hide
*/
+ @UserHandleAware
public boolean isSupervisionEnabled() {
try {
- return mService.isSupervisionEnabled();
+ return mService.isSupervisionEnabledForUser(mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
-
-
}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 91cdf8d..1c9be6f 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -76,11 +76,15 @@
* PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is
* absent, {@link Context#getSystemService} may return null.
*/
-@SystemService(Context.VCN_MANAGEMENT_SERVICE)
+@SystemService(VcnManager.VCN_MANAGEMENT_SERVICE_STRING)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public class VcnManager {
@NonNull private static final String TAG = VcnManager.class.getSimpleName();
+ // TODO: b/366598445: Expose and use Context.VCN_MANAGEMENT_SERVICE
+ /** @hide */
+ public static final String VCN_MANAGEMENT_SERVICE_STRING = "vcn_management";
+
/**
* Key for WiFi entry RSSI thresholds
*
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 1fef602..a698b9d 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -788,6 +788,12 @@
/** Parses an XML representation of BatteryUsageStats */
public static BatteryUsageStats createFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
+ return createBuilderFromXml(parser).build();
+ }
+
+ /** Parses an XML representation of BatteryUsageStats */
+ public static BatteryUsageStats.Builder createBuilderFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
Builder builder = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
@@ -862,7 +868,7 @@
eventType = parser.next();
}
- return builder.build();
+ return builder;
}
@Override
@@ -978,10 +984,21 @@
*/
@NonNull
public BatteryUsageStats build() {
+ if (mBatteryConsumersCursorWindow == null) {
+ throw new IllegalStateException("Builder has been discarded");
+ }
return new BatteryUsageStats(this);
}
/**
+ * Close this builder without actually calling ".build()". Do not attempt
+ * to continue using the builder after this call.
+ */
+ public void discard() {
+ mBatteryConsumersCursorWindow.close();
+ }
+
+ /**
* Sets the battery capacity in milli-amp-hours.
*/
public Builder setBatteryCapacity(double batteryCapacityMah) {
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index a12606b..b533225 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -77,6 +77,8 @@
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE = 0x0040;
+ public static final int FLAG_BATTERY_USAGE_STATS_ACCUMULATED = 0x0080;
+
private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
private final int mFlags;
@@ -328,6 +330,15 @@
}
/**
+ * Requests the full continuously accumulated battery usage stats: across reboots
+ * and most battery stats resets.
+ */
+ public Builder accumulated() {
+ mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED;
+ return this;
+ }
+
+ /**
* Requests to aggregate stored snapshots between the two supplied timestamps
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index cfbf528..a5697fb 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -16,6 +16,8 @@
package android.os;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
+
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -138,14 +140,14 @@
Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason=" + reason);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "vibrate");
try {
mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason,
mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -155,14 +157,14 @@
Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback, reason=" + reason);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedback");
try {
mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant,
reason, flags, privFlags);
} catch (RemoteException e) {
Log.w(TAG, "Failed to perform haptic feedback.", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -174,15 +176,14 @@
+ " no vibrator manager service.");
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR,
- "performHapticFeedbackForInputDevice, reason=" + reason);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
try {
mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName,
constant, inputDeviceId, inputSource, reason, flags, privFlags);
} catch (RemoteException e) {
Log.w(TAG, "Failed to perform haptic feedback for input device.", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index c38ee08..325d274 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -10,3 +10,4 @@
per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS
per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS
per-file *.aconfig = victorhsieh@google.com,eranm@google.com
+per-file *responsible_apis_flags.aconfig = haok@google.com
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 88da8eb..48d7cf7 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -18,6 +18,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -90,10 +91,11 @@
= "android.service.notification.NotificationAssistantService";
/**
- * Activity Action: Show notification assistant detail setting page in NAS app.
+ * Activity Action: Show notification assistant detail setting page in the NAS app.
* <p>
- * In some cases, a matching Activity may not exist, so ensure you
- * safeguard against this.
+ * To be implemented by the NAS to offer users additional customization of intelligence
+ * features. If the action is not implemented, the OS will not provide a link to it in the
+ * Settings UI.
* <p>
* Input: Nothing.
* <p>
@@ -103,6 +105,30 @@
public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS =
"android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
+ /**
+ * Activity Action: Open notification assistant feedback page in the NAS app.
+ * <p>
+ * If the NAS does not implement this page, the OS will not show any feedback calls to action in
+ * the UI.
+ * <p>
+ * Input: {@link #EXTRA_NOTIFICATION_KEY}, the {@link StatusBarNotification#getKey()} of the
+ * notification the user wants to file feedback for.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS =
+ "android.service.notification.action.NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS";
+
+ /**
+ * A string extra containing the key of the notification that the user triggered feedback for.
+ *
+ * Extra for {@link #ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS}.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String EXTRA_NOTIFICATION_KEY
+ = "android.service.notification.extra.NOTIFICATION_KEY";
/**
* Data type: int, the feedback rating score provided by user. The score can be any integer
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index cc5e583..fbc30ed 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -287,6 +287,13 @@
}
flag {
+ name: "enable_fully_immersive_in_desktop"
+ namespace: "lse_desktop_experience"
+ description: "Enabled the fully immersive experience from desktop"
+ bug: "359523924"
+}
+
+flag {
name: "enable_display_focus_in_shell_transitions"
namespace: "lse_desktop_experience"
description: "Creates a shell transition when display focus switches."
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
index c8b7def..702e5e2 100644
--- a/core/java/com/android/internal/util/ScreenshotRequest.java
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -33,6 +33,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.view.Display;
import android.view.WindowManager;
import java.util.Objects;
@@ -53,11 +54,12 @@
private final Bitmap mBitmap;
private final Rect mBoundsInScreen;
private final Insets mInsets;
+ private final int mDisplayId;
private ScreenshotRequest(
@WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
ComponentName topComponent, int taskId, int userId,
- Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+ Bitmap bitmap, Rect boundsInScreen, Insets insets, int displayId) {
mType = type;
mSource = source;
mTopComponent = topComponent;
@@ -66,6 +68,7 @@
mBitmap = bitmap;
mBoundsInScreen = boundsInScreen;
mInsets = insets;
+ mDisplayId = displayId;
}
ScreenshotRequest(Parcel in) {
@@ -77,6 +80,7 @@
mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
mInsets = in.readTypedObject(Insets.CREATOR);
+ mDisplayId = in.readInt();
}
@WindowManager.ScreenshotType
@@ -113,6 +117,10 @@
return mTopComponent;
}
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -128,6 +136,7 @@
dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
dest.writeTypedObject(mBoundsInScreen, 0);
dest.writeTypedObject(mInsets, 0);
+ dest.writeInt(mDisplayId);
}
@NonNull
@@ -161,6 +170,7 @@
private int mTaskId = INVALID_TASK_ID;
private int mUserId = USER_NULL;
private ComponentName mTopComponent;
+ private int mDisplayId = Display.INVALID_DISPLAY;
/**
* Begin building a ScreenshotRequest.
@@ -193,7 +203,7 @@
}
return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
- mBoundsInScreen, mInsets);
+ mBoundsInScreen, mInsets, mDisplayId);
}
/**
@@ -255,6 +265,16 @@
mInsets = insets;
return this;
}
+
+ /**
+ * Set the display ID for this request.
+ *
+ * @param displayId see {@link Display}
+ */
+ public Builder setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ return this;
+ }
}
/**
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
index 89acbc7..0ce403e 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -35,6 +35,7 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Parcel;
+import android.view.Display;
import androidx.test.runner.AndroidJUnit4;
@@ -64,10 +65,12 @@
assertNull("Bitmap was expected to be null", out.getBitmap());
assertNull("Bounds were expected to be null", out.getBoundsInScreen());
assertEquals(Insets.NONE, out.getInsets());
+ assertEquals(Display.INVALID_DISPLAY, out.getDisplayId());
}
@Test
public void testProvidedScreenshot() {
+ int displayId = 5;
Bitmap bitmap = makeHardwareBitmap(50, 50);
ScreenshotRequest in =
new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
@@ -77,6 +80,7 @@
.setBitmap(bitmap)
.setBoundsOnScreen(new Rect(10, 10, 60, 60))
.setInsets(Insets.of(2, 3, 4, 5))
+ .setDisplayId(displayId)
.build();
Parcel parcel = Parcel.obtain();
@@ -92,6 +96,7 @@
assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+ assertEquals(displayId, out.getDisplayId());
}
@Test
diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp
new file mode 100644
index 0000000..6f5eff3
--- /dev/null
+++ b/libs/appfunctions/tests/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "AppFunctionsSidecarTestCases",
+ team: "trendy_team_system_intelligence",
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.core_core-ktx",
+ "com.google.android.appfunctions.sidecar.impl",
+ "junit",
+ "kotlin-test",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "testables",
+ "testng",
+ "truth",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+}
diff --git a/libs/appfunctions/tests/AndroidManifest.xml b/libs/appfunctions/tests/AndroidManifest.xml
new file mode 100644
index 0000000..9a7d460
--- /dev/null
+++ b/libs/appfunctions/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.appfunctions.sidecar.tests">
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.mock" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.google.android.appfunctions.sidecar.tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/appfunctions/tests/AndroidTest.xml b/libs/appfunctions/tests/AndroidTest.xml
new file mode 100644
index 0000000..8251212
--- /dev/null
+++ b/libs/appfunctions/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Config for AppFunctions Sidecar Tests">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="AppFunctionsSidecarTestCases.apk" />
+ </target_preparer>
+ <option name="test-tag" value="AppFunctionsSidecarTestCases" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.google.android.appfunctions.sidecar.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
new file mode 100644
index 0000000..1f9fddd
--- /dev/null
+++ b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 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.google.android.appfunctions.sidecar.tests
+
+import android.app.appfunctions.ExecuteAppFunctionRequest
+import android.app.appfunctions.ExecuteAppFunctionResponse
+import android.app.appsearch.GenericDocument
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.android.appfunctions.sidecar.SidecarConverter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SidecarConverterTest {
+ @Test
+ fun getSidecarExecuteAppFunctionRequest_sameContents() {
+ val extras = Bundle()
+ extras.putString("extra", "value")
+ val parameters: GenericDocument =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("testLong", 23)
+ .build()
+ val platformRequest: ExecuteAppFunctionRequest =
+ ExecuteAppFunctionRequest.Builder("targetPkg", "targetFunctionId")
+ .setExtras(extras)
+ .setParameters(parameters)
+ .build()
+
+ val sidecarRequest = SidecarConverter.getSidecarExecuteAppFunctionRequest(platformRequest)
+
+ assertThat(sidecarRequest.targetPackageName).isEqualTo("targetPkg")
+ assertThat(sidecarRequest.functionIdentifier).isEqualTo("targetFunctionId")
+ assertThat(sidecarRequest.parameters).isEqualTo(parameters)
+ assertThat(sidecarRequest.extras.size()).isEqualTo(1)
+ assertThat(sidecarRequest.extras.getString("extra")).isEqualTo("value")
+ }
+
+ @Test
+ fun getPlatformExecuteAppFunctionRequest_sameContents() {
+ val extras = Bundle()
+ extras.putString("extra", "value")
+ val parameters: GenericDocument =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("testLong", 23)
+ .build()
+ val sidecarRequest =
+ com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder(
+ "targetPkg",
+ "targetFunctionId"
+ )
+ .setExtras(extras)
+ .setParameters(parameters)
+ .build()
+
+ val platformRequest = SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest)
+
+ assertThat(platformRequest.targetPackageName).isEqualTo("targetPkg")
+ assertThat(platformRequest.functionIdentifier).isEqualTo("targetFunctionId")
+ assertThat(platformRequest.parameters).isEqualTo(parameters)
+ assertThat(platformRequest.extras.size()).isEqualTo(1)
+ assertThat(platformRequest.extras.getString("extra")).isEqualTo("value")
+ }
+
+ @Test
+ fun getSidecarExecuteAppFunctionResponse_successResponse_sameContents() {
+ val resultGd: GenericDocument =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
+ .build()
+ val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null)
+
+ val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
+ platformResponse
+ )
+
+ assertThat(sidecarResponse.isSuccess).isTrue()
+ assertThat(
+ sidecarResponse.resultDocument.getProperty(
+ ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
+ )
+ )
+ .isEqualTo(booleanArrayOf(true))
+ assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
+ assertThat(sidecarResponse.errorMessage).isNull()
+ }
+
+ @Test
+ fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() {
+ val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
+ val platformResponse =
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ null,
+ null
+ )
+
+ val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
+ platformResponse
+ )
+
+ assertThat(sidecarResponse.isSuccess).isFalse()
+ assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
+ assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
+ assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
+ assertThat(sidecarResponse.resultCode)
+ .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+ assertThat(sidecarResponse.errorMessage).isNull()
+ }
+
+ @Test
+ fun getPlatformExecuteAppFunctionResponse_successResponse_sameContents() {
+ val resultGd: GenericDocument =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
+ .build()
+ val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse
+ .newSuccess(resultGd, null)
+
+ val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
+ sidecarResponse
+ )
+
+ assertThat(platformResponse.isSuccess).isTrue()
+ assertThat(
+ platformResponse.resultDocument.getProperty(
+ ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
+ )
+ )
+ .isEqualTo(booleanArrayOf(true))
+ assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
+ assertThat(platformResponse.errorMessage).isNull()
+ }
+
+ @Test
+ fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
+ val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
+ val sidecarResponse =
+ com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ null,
+ null
+ )
+
+ val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
+ sidecarResponse
+ )
+
+ assertThat(platformResponse.isSuccess).isFalse()
+ assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
+ assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
+ assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
+ assertThat(platformResponse.resultCode)
+ .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+ assertThat(platformResponse.errorMessage).isNull()
+ }
+}
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 71ecb4c..37a0e79 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -19,6 +19,7 @@
static_libs: [
"androidx.preference_preference",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
new file mode 100644
index 0000000..fc63c0f
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_extrasmall4"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/action1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button1"
+ style="@style/SettingsLibActionButton.Expressive"
+ android:layout_width="@dimen/settingslib_expressive_space_large3"
+ android:layout_height="@dimen/settingslib_expressive_space_medium5"
+ android:layout_gravity="center_horizontal" />
+ <TextView
+ android:id="@+id/text1"
+ style="@style/SettingsLibActionButton.Expressive.Label"
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/action2"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button2"
+ style="@style/SettingsLibActionButton.Expressive"
+ android:layout_width="@dimen/settingslib_expressive_space_large3"
+ android:layout_height="@dimen/settingslib_expressive_space_medium5"
+ android:layout_gravity="center_horizontal" />
+ <TextView
+ android:id="@+id/text2"
+ style="@style/SettingsLibActionButton.Expressive.Label"
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/action3"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button3"
+ style="@style/SettingsLibActionButton.Expressive"
+ android:layout_width="@dimen/settingslib_expressive_space_large3"
+ android:layout_height="@dimen/settingslib_expressive_space_medium5"
+ android:layout_gravity="center_horizontal" />
+ <TextView
+ android:id="@+id/text3"
+ style="@style/SettingsLibActionButton.Expressive.Label"
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/action4"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button4"
+ style="@style/SettingsLibActionButton.Expressive"
+ android:layout_width="@dimen/settingslib_expressive_space_large3"
+ android:layout_height="@dimen/settingslib_expressive_space_medium5"
+ android:layout_gravity="center_horizontal" />
+ <TextView
+ android:id="@+id/text4"
+ style="@style/SettingsLibActionButton.Expressive.Label"
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
new file mode 100644
index 0000000..cc948a6
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+ -->
+
+<resources>
+ <style name="SettingsLibActionButton.Expressive" parent="SettingsLibButtonStyle.Expressive.Tonal">
+ <item name="android:backgroundTint">@color/settingslib_materialColorPrimaryContainer</item>
+ <item name="iconTint">@color/settingslib_materialColorOnPrimaryContainer</item>
+ <item name="iconGravity">textTop</item>
+ </style>
+
+ <style name="SettingsLibActionButton.Expressive.Label" parent="SettingsLibTextAppearance.Emphasized.Title.Small">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_small3</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_small3</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="android:layout_gravity">center</item>
+ </style>
+
+</resources>
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index 5dc11cf..f011039 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -26,6 +26,8 @@
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
@@ -34,6 +36,8 @@
import com.android.settingslib.widget.preference.actionbuttons.R;
+import com.google.android.material.button.MaterialButton;
+
import java.util.ArrayList;
import java.util.List;
@@ -98,7 +102,10 @@
}
private void init() {
- setLayoutResource(R.layout.settingslib_action_buttons);
+ int resId = SettingsThemeHelper.isExpressiveTheme(getContext())
+ ? R.layout.settingslib_expressive_action_buttons
+ : R.layout.settingslib_action_buttons;
+ setLayoutResource(resId);
setSelectable(false);
final Resources res = getContext().getResources();
@@ -127,6 +134,21 @@
mButton3Info.mButton = (Button) holder.findViewById(R.id.button3);
mButton4Info.mButton = (Button) holder.findViewById(R.id.button4);
+ if (SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ mButton1Info.mIsExpressive = true;
+ mButton1Info.mTextView = (TextView) holder.findViewById(R.id.text1);
+ mButton1Info.mActionLayout = (LinearLayout) holder.findViewById(R.id.action1);
+ mButton2Info.mIsExpressive = true;
+ mButton2Info.mTextView = (TextView) holder.findViewById(R.id.text2);
+ mButton2Info.mActionLayout = (LinearLayout) holder.findViewById(R.id.action2);
+ mButton3Info.mIsExpressive = true;
+ mButton3Info.mTextView = (TextView) holder.findViewById(R.id.text3);
+ mButton3Info.mActionLayout = (LinearLayout) holder.findViewById(R.id.action3);
+ mButton4Info.mIsExpressive = true;
+ mButton4Info.mTextView = (TextView) holder.findViewById(R.id.text4);
+ mButton4Info.mActionLayout = (LinearLayout) holder.findViewById(R.id.action4);
+ }
+
mDivider1 = holder.findViewById(R.id.divider1);
mDivider2 = holder.findViewById(R.id.divider2);
mDivider3 = holder.findViewById(R.id.divider3);
@@ -169,45 +191,47 @@
mVisibleButtonInfos.add(mButton4Info);
}
- final boolean isRtl = getContext().getResources().getConfiguration()
- .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- switch (mVisibleButtonInfos.size()) {
- case SINGLE_BUTTON_STYLE :
- if (isRtl) {
- setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle1);
- } else {
- setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle1);
- }
- break;
- case TWO_BUTTONS_STYLE :
- if (isRtl) {
- setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle2);
- } else {
- setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle2);
- }
- break;
- case THREE_BUTTONS_STYLE :
- if (isRtl) {
- setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle3);
- } else {
- setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle3);
- }
- break;
- case FOUR_BUTTONS_STYLE :
- if (isRtl) {
- setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle4);
- } else {
- setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle4);
- }
- break;
- default:
- Log.e(TAG, "No visible buttons info, skip background settings.");
- break;
- }
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ final boolean isRtl = getContext().getResources().getConfiguration()
+ .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ switch (mVisibleButtonInfos.size()) {
+ case SINGLE_BUTTON_STYLE :
+ if (isRtl) {
+ setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle1);
+ } else {
+ setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle1);
+ }
+ break;
+ case TWO_BUTTONS_STYLE :
+ if (isRtl) {
+ setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle2);
+ } else {
+ setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle2);
+ }
+ break;
+ case THREE_BUTTONS_STYLE :
+ if (isRtl) {
+ setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle3);
+ } else {
+ setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle3);
+ }
+ break;
+ case FOUR_BUTTONS_STYLE :
+ if (isRtl) {
+ setupRtlBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle4);
+ } else {
+ setupBackgrounds(mVisibleButtonInfos, mBtnBackgroundStyle4);
+ }
+ break;
+ default:
+ Log.e(TAG, "No visible buttons info, skip background settings.");
+ break;
+ }
- setupDivider1();
- setupDivider2();
- setupDivider3();
+ setupDivider1();
+ setupDivider2();
+ setupDivider3();
+ }
}
private void setupBackgrounds(
@@ -509,23 +533,43 @@
static class ButtonInfo {
private Button mButton;
+ private TextView mTextView;
+ private LinearLayout mActionLayout;
private CharSequence mText;
private Drawable mIcon;
private View.OnClickListener mListener;
private boolean mIsEnabled = true;
private boolean mIsVisible = true;
+ private boolean mIsExpressive = false;
void setUpButton() {
- mButton.setText(mText);
+ if (mIsExpressive) {
+ mTextView.setText(mText);
+ if (mButton instanceof MaterialButton) {
+ ((MaterialButton) mButton).setIcon(mIcon);
+ }
+ } else {
+ mButton.setText(mText);
+ mButton.setCompoundDrawablesWithIntrinsicBounds(
+ null /* left */, mIcon /* top */, null /* right */, null /* bottom */);
+ }
+
mButton.setOnClickListener(mListener);
mButton.setEnabled(mIsEnabled);
- mButton.setCompoundDrawablesWithIntrinsicBounds(
- null /* left */, mIcon /* top */, null /* right */, null /* bottom */);
+
if (shouldBeVisible()) {
mButton.setVisibility(View.VISIBLE);
+ if (mIsExpressive) {
+ mTextView.setVisibility(View.VISIBLE);
+ mActionLayout.setVisibility(View.VISIBLE);
+ }
} else {
mButton.setVisibility(View.GONE);
+ if (mIsExpressive) {
+ mTextView.setVisibility(View.GONE);
+ mActionLayout.setVisibility(View.GONE);
+ }
}
}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
index 2320aab..0542c51 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
@@ -48,6 +48,7 @@
<dimen name="settingslib_expressive_space_medium2">36dp</dimen>
<dimen name="settingslib_expressive_space_medium3">40dp</dimen>
<dimen name="settingslib_expressive_space_medium4">48dp</dimen>
+ <dimen name="settingslib_expressive_space_medium5">56dp</dimen>
<dimen name="settingslib_expressive_space_large1">60dp</dimen>
<dimen name="settingslib_expressive_space_large2">64dp</dimen>
<dimen name="settingslib_expressive_space_large3">72dp</dimen>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 04ae80e..442def9 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -169,4 +169,23 @@
<item name="android:focusable">false</item>
<item name="thumbIcon">@drawable/settingslib_expressive_switch_thumb_icon</item>
</style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Tonal"
+ parent="@style/Widget.Material3.Button.TonalButton">
+ <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:backgroundTint">@color/settingslib_materialColorSecondaryContainer</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="android:textSize">14sp</item>
+ <item name="iconGravity">textStart</item>
+ <item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/animation/lib/Android.bp b/packages/SystemUI/animation/lib/Android.bp
index 4324d463..d9230ec 100644
--- a/packages/SystemUI/animation/lib/Android.bp
+++ b/packages/SystemUI/animation/lib/Android.bp
@@ -33,6 +33,20 @@
],
}
+// This is the core animation library written in java and can be depended by java sdk libraries.
+// Please don't introduce kotlin code in this target since kotlin is incompatible with sdk
+// libraries.
+java_library {
+ name: "PlatformAnimationLib-core",
+ srcs: [
+ "src/com/android/systemui/animation/*.java",
+ ":PlatformAnimationLib-aidl",
+ ],
+ static_libs: [
+ "WindowManager-Shell-shared",
+ ],
+}
+
filegroup {
name: "PlatformAnimationLib-aidl",
srcs: [
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
new file mode 100644
index 0000000..64bedd3
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2024 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.animation;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.ActivityOptions.LaunchCookie;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.RemoteException;
+import android.util.Log;
+import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
+
+import com.android.systemui.animation.shared.IOriginTransitions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A session object that holds origin transition states for starting an activity from an on-screen
+ * UI component and smoothly transitioning back from the activity to the same UI component.
+ */
+public class OriginTransitionSession {
+ private static final String TAG = "OriginTransitionSession";
+ static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {NOT_STARTED, STARTED, CANCELLED})
+ private @interface State {}
+
+ @State private static final int NOT_STARTED = 0;
+ @State private static final int STARTED = 1;
+ @State private static final int CANCELLED = 5;
+
+ private final String mName;
+ @Nullable private final IOriginTransitions mOriginTransitions;
+ private final Predicate<RemoteTransition> mIntentStarter;
+ @Nullable private final IRemoteTransition mEntryTransition;
+ @Nullable private final IRemoteTransition mExitTransition;
+ private final AtomicInteger mState = new AtomicInteger(NOT_STARTED);
+
+ @Nullable private RemoteTransition mOriginTransition;
+
+ private OriginTransitionSession(
+ String name,
+ @Nullable IOriginTransitions originTransitions,
+ Predicate<RemoteTransition> intentStarter,
+ @Nullable IRemoteTransition entryTransition,
+ @Nullable IRemoteTransition exitTransition) {
+ mName = name;
+ mOriginTransitions = originTransitions;
+ mIntentStarter = intentStarter;
+ mEntryTransition = entryTransition;
+ mExitTransition = exitTransition;
+ if (hasExitTransition() && !hasEntryTransition()) {
+ throw new IllegalArgumentException(
+ "Entry transition must be supplied if you want to play an exit transition!");
+ }
+ }
+
+ /**
+ * Launch the target intent with the supplied entry transition. After this method, the entry
+ * transition is expected to receive callbacks. The exit transition will be registered and
+ * triggered when the system detects a return from the launched activity to the launching
+ * activity.
+ */
+ public boolean start() {
+ if (!mState.compareAndSet(NOT_STARTED, STARTED)) {
+ logE("start: illegal state - " + stateToString(mState.get()));
+ return false;
+ }
+
+ RemoteTransition remoteTransition = null;
+ if (hasEntryTransition() && hasExitTransition()) {
+ logD("start: starting with entry and exit transition.");
+ try {
+ remoteTransition =
+ mOriginTransition =
+ mOriginTransitions.makeOriginTransition(
+ new RemoteTransition(mEntryTransition, mName + "-entry"),
+ new RemoteTransition(mExitTransition, mName + "-exit"));
+ } catch (RemoteException e) {
+ logE("Unable to create origin transition!", e);
+ }
+ } else if (hasEntryTransition()) {
+ logD("start: starting with entry transition.");
+ remoteTransition = new RemoteTransition(mEntryTransition, mName + "-entry");
+
+ } else {
+ logD("start: starting without transition.");
+ }
+ if (mIntentStarter.test(remoteTransition)) {
+ return true;
+ } else {
+ // Animation is cancelled by intent starter.
+ logD("start: cancelled by intent starter!");
+ cancel();
+ return false;
+ }
+ }
+
+ /**
+ * Cancel the current transition and the registered exit transition if it exists. After this
+ * method, this session object can no longer be used. Clients need to create a new session
+ * object if they want to launch another intent with origin transition.
+ */
+ public void cancel() {
+ final int lastState = mState.getAndSet(CANCELLED);
+ if (lastState == CANCELLED || lastState == NOT_STARTED) {
+ return;
+ }
+ logD("cancel: cancelled transition. Last state: " + stateToString(lastState));
+ if (mOriginTransition != null) {
+ try {
+ mOriginTransitions.cancelOriginTransition(mOriginTransition);
+ mOriginTransition = null;
+ } catch (RemoteException e) {
+ logE("Unable to cancel origin transition!", e);
+ }
+ }
+ }
+
+ private boolean hasEntryTransition() {
+ return mEntryTransition != null;
+ }
+
+ private boolean hasExitTransition() {
+ return mOriginTransitions != null && mExitTransition != null;
+ }
+
+ private void logD(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, "Session[" + mName + "] - " + msg);
+ }
+ }
+
+ private void logE(String msg) {
+ Log.e(TAG, "Session[" + mName + "] - " + msg);
+ }
+
+ private void logE(String msg, Throwable e) {
+ Log.e(TAG, "Session[" + mName + "] - " + msg, e);
+ }
+
+ private static String stateToString(@State int state) {
+ switch (state) {
+ case NOT_STARTED:
+ return "NOT_STARTED";
+ case STARTED:
+ return "STARTED";
+ case CANCELLED:
+ return "CANCELLED";
+ default:
+ return "UNKNOWN(" + state + ")";
+ }
+ }
+
+ /** A builder to build a {@link OriginTransitionSession}. */
+ public static class Builder {
+ private final Context mContext;
+ @Nullable private final IOriginTransitions mOriginTransitions;
+ @Nullable private Supplier<IRemoteTransition> mEntryTransitionSupplier;
+ @Nullable private Supplier<IRemoteTransition> mExitTransitionSupplier;
+ private String mName;
+ @Nullable private Predicate<RemoteTransition> mIntentStarter;
+
+ /** Create a builder that only supports entry transition. */
+ public Builder(Context context) {
+ this(context, /* originTransitions= */ null);
+ }
+
+ /** Create a builder that supports both entry and return transition. */
+ public Builder(Context context, @Nullable IOriginTransitions originTransitions) {
+ mContext = context;
+ mOriginTransitions = originTransitions;
+ mName = context.getPackageName();
+ }
+
+ /** Specify a name that is used in logging. */
+ public Builder withName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /** Specify an intent that will be launched when the session started. */
+ public Builder withIntent(Intent intent) {
+ return withIntentStarter(
+ transition -> {
+ mContext.startActivity(
+ intent, createDefaultActivityOptions(transition).toBundle());
+ return true;
+ });
+ }
+
+ /** Specify a pending intent that will be launched when the session started. */
+ public Builder withPendingIntent(PendingIntent pendingIntent) {
+ return withIntentStarter(
+ transition -> {
+ try {
+ pendingIntent.send(createDefaultActivityOptions(transition).toBundle());
+ return true;
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Failed to launch pending intent!", e);
+ return false;
+ }
+ });
+ }
+
+ private static ActivityOptions createDefaultActivityOptions(
+ @Nullable RemoteTransition transition) {
+ ActivityOptions options =
+ transition == null
+ ? ActivityOptions.makeBasic()
+ : ActivityOptions.makeRemoteTransition(transition);
+ LaunchCookie cookie = new LaunchCookie();
+ options.setLaunchCookie(cookie);
+ return options;
+ }
+
+ /**
+ * Specify an intent starter function that will be called to start an activity. The function
+ * accepts an optional {@link RemoteTransition} object which can be used to create an {@link
+ * ActivityOptions} for the activity launch. The function can also return a {@code false}
+ * result to cancel the session.
+ *
+ * <p>Note: it's encouraged to use {@link #withIntent(Intent)} or {@link
+ * #withPendingIntent(PendingIntent)} instead of this method. Use it only if the default
+ * activity launch code doesn't satisfy your requirement.
+ */
+ public Builder withIntentStarter(Predicate<RemoteTransition> intentStarter) {
+ mIntentStarter = intentStarter;
+ return this;
+ }
+
+ /** Add an entry transition to the builder. */
+ public Builder withEntryTransition(IRemoteTransition transition) {
+ mEntryTransitionSupplier = () -> transition;
+ return this;
+ }
+
+ /** Add an exit transition to the builder. */
+ public Builder withExitTransition(IRemoteTransition transition) {
+ mExitTransitionSupplier = () -> transition;
+ return this;
+ }
+
+ /** Build an {@link OriginTransitionSession}. */
+ public OriginTransitionSession build() {
+ if (mIntentStarter == null) {
+ throw new IllegalArgumentException("No intent, pending intent, or intent starter!");
+ }
+ return new OriginTransitionSession(
+ mName,
+ mOriginTransitions,
+ mIntentStarter,
+ mEntryTransitionSupplier == null ? null : mEntryTransitionSupplier.get(),
+ mExitTransitionSupplier == null ? null : mExitTransitionSupplier.get());
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/lib/tests/Android.bp b/packages/SystemUI/animation/lib/tests/Android.bp
new file mode 100644
index 0000000..c1a3e84
--- /dev/null
+++ b/packages/SystemUI/animation/lib/tests/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "PlatformAnimationLibCoreTests",
+
+ defaults: [
+ "platform_app_defaults",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ static_libs: [
+ "PlatformAnimationLib-core",
+ "platform-test-rules",
+ "testables",
+ ],
+ compile_multilib: "both",
+ libs: [
+ "android.test.runner.stubs.system",
+ "android.test.base.stubs.system",
+ ],
+
+ certificate: "platform",
+
+ manifest: "AndroidManifest.xml",
+}
diff --git a/packages/SystemUI/animation/lib/tests/AndroidManifest.xml b/packages/SystemUI/animation/lib/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1788abf
--- /dev/null
+++ b/packages/SystemUI/animation/lib/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:sharedUserId="android.uid.system"
+ package="com.android.systemui.animation.core.tests" >
+
+ <application android:debuggable="true" android:testOnly="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.testing.TestableInstrumentation"
+ android:targetPackage="com.android.systemui.animation.core.tests"
+ android:label="Tests for PlatformAnimationLib-core" />
+</manifest>
diff --git a/packages/SystemUI/animation/lib/tests/AndroidTest.xml b/packages/SystemUI/animation/lib/tests/AndroidTest.xml
new file mode 100644
index 0000000..0f37d7a
--- /dev/null
+++ b/packages/SystemUI/animation/lib/tests/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<configuration description="Runs Tests for PlatformAnimationLib-core.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PlatformAnimationLibCoreTests.apk" />
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <!-- Among other reasons, root access is needed for screen recording artifacts. -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.systemui.animation.core.tests" />
+ <option name="runner" value="android.testing.TestableInstrumentation" />
+ <option name="test-filter-dir" value="/data/data/com.android.systemui.animation.core.tests" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.systemui.animation.core.tests/files" />
+ <option name="collect-on-run-ended-only" value="false" />
+ </metrics_collector>
+</configuration>
diff --git a/packages/SystemUI/animation/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java b/packages/SystemUI/animation/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java
new file mode 100644
index 0000000..287e53b
--- /dev/null
+++ b/packages/SystemUI/animation/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2024 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.animation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.Nullable;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.animation.shared.IOriginTransitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Map;
+import java.util.function.Predicate;
+
+/** Unit tests for {@link OriginTransitionSession}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public final class OriginTransitionSessionTest {
+ private static final ComponentName TEST_ACTIVITY_1 = new ComponentName("test", "Activity1");
+ private static final ComponentName TEST_ACTIVITY_2 = new ComponentName("test", "Activity2");
+ private static final ComponentName TEST_ACTIVITY_3 = new ComponentName("test", "Activity3");
+
+ private FakeIOriginTransitions mIOriginTransitions;
+ private Instrumentation mInstrumentation;
+ private FakeIntentStarter mIntentStarter;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
+ mIOriginTransitions = new FakeIOriginTransitions();
+ mIntentStarter = new FakeIntentStarter(TEST_ACTIVITY_1, TEST_ACTIVITY_2);
+ }
+
+ @Test
+ public void sessionStart_withEntryAndExitTransition_transitionsPlayed() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ FakeRemoteTransition exit = new FakeRemoteTransition();
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .withExitTransition(exit)
+ .build();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isTrue();
+ assertThat(entry.started()).isTrue();
+
+ runReturnTransition(mIntentStarter);
+
+ assertThat(exit.started()).isTrue();
+ }
+
+ @Test
+ public void sessionStart_withEntryTransition_transitionPlayed() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .build();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isTrue();
+ assertThat(entry.started()).isTrue();
+ assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse();
+ }
+
+ @Test
+ public void sessionStart_withoutTransition_launchedIntent() {
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .build();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isTrue();
+ assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse();
+ }
+
+ @Test
+ public void sessionStart_cancelledByIntentStarter_transitionNotPlayed() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ FakeRemoteTransition exit = new FakeRemoteTransition();
+ mIntentStarter =
+ new FakeIntentStarter(TEST_ACTIVITY_1, TEST_ACTIVITY_2, /* result= */ false);
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .withExitTransition(exit)
+ .build();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isFalse();
+ assertThat(entry.started()).isFalse();
+ assertThat(exit.started()).isFalse();
+ assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse();
+ }
+
+ @Test
+ public void sessionStart_alreadyStarted_noOp() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ FakeRemoteTransition exit = new FakeRemoteTransition();
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .withExitTransition(exit)
+ .build();
+ session.start();
+ entry.reset();
+ mIntentStarter.reset();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isFalse();
+ assertThat(entry.started()).isFalse();
+ }
+
+ @Test
+ public void sessionStart_alreadyCancelled_noOp() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ FakeRemoteTransition exit = new FakeRemoteTransition();
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .withExitTransition(exit)
+ .build();
+ session.cancel();
+
+ session.start();
+
+ assertThat(mIntentStarter.hasLaunched()).isFalse();
+ assertThat(entry.started()).isFalse();
+ assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse();
+ }
+
+ @Test
+ public void sessionCancelled_returnTransitionNotPlayed() {
+ FakeRemoteTransition entry = new FakeRemoteTransition();
+ FakeRemoteTransition exit = new FakeRemoteTransition();
+ OriginTransitionSession session =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(mIntentStarter)
+ .withEntryTransition(entry)
+ .withExitTransition(exit)
+ .build();
+
+ session.start();
+ session.cancel();
+
+ assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse();
+ }
+
+ @Test
+ public void multipleSessionsStarted_allTransitionsPlayed() {
+ FakeRemoteTransition entry1 = new FakeRemoteTransition();
+ FakeRemoteTransition exit1 = new FakeRemoteTransition();
+ FakeIntentStarter starter1 = mIntentStarter;
+ OriginTransitionSession session1 =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(starter1)
+ .withEntryTransition(entry1)
+ .withExitTransition(exit1)
+ .build();
+ FakeRemoteTransition entry2 = new FakeRemoteTransition();
+ FakeRemoteTransition exit2 = new FakeRemoteTransition();
+ FakeIntentStarter starter2 = new FakeIntentStarter(TEST_ACTIVITY_2, TEST_ACTIVITY_3);
+ OriginTransitionSession session2 =
+ new OriginTransitionSession.Builder(mContext, mIOriginTransitions)
+ .withIntentStarter(starter2)
+ .withEntryTransition(entry2)
+ .withExitTransition(exit2)
+ .build();
+
+ session1.start();
+
+ assertThat(starter1.hasLaunched()).isTrue();
+ assertThat(entry1.started()).isTrue();
+
+ session2.start();
+
+ assertThat(starter2.hasLaunched()).isTrue();
+ assertThat(entry2.started()).isTrue();
+
+ runReturnTransition(starter2);
+
+ assertThat(exit2.started()).isTrue();
+
+ runReturnTransition(starter1);
+
+ assertThat(exit1.started()).isTrue();
+ }
+
+ private void runReturnTransition(FakeIntentStarter intentStarter) {
+ TransitionInfo info =
+ buildTransitionInfo(intentStarter.getToActivity(), intentStarter.getFromActivity());
+ mIOriginTransitions.runReturnTransition(intentStarter.getTransitionOfLastLaunch(), info);
+ }
+
+ private static TransitionInfo buildTransitionInfo(ComponentName from, ComponentName to) {
+ TransitionInfo info = new TransitionInfo(WindowManager.TRANSIT_OPEN, /* flags= */ 0);
+ TransitionInfo.Change c1 =
+ new TransitionInfo.Change(/* container= */ null, /* leash= */ null);
+ c1.setMode(WindowManager.TRANSIT_OPEN);
+ c1.setActivityComponent(to);
+ TransitionInfo.Change c2 =
+ new TransitionInfo.Change(/* container= */ null, /* leash= */ null);
+ c2.setMode(WindowManager.TRANSIT_CLOSE);
+ c2.setActivityComponent(from);
+ info.addChange(c2);
+ info.addChange(c1);
+ return info;
+ }
+
+ private static class FakeIntentStarter implements Predicate<RemoteTransition> {
+ private final ComponentName mFromActivity;
+ private final ComponentName mToActivity;
+ private final boolean mResult;
+
+ @Nullable private RemoteTransition mTransition;
+ private boolean mLaunched;
+
+ FakeIntentStarter(ComponentName from, ComponentName to) {
+ this(from, to, /* result= */ true);
+ }
+
+ FakeIntentStarter(ComponentName from, ComponentName to, boolean result) {
+ mFromActivity = from;
+ mToActivity = to;
+ mResult = result;
+ }
+
+ @Override
+ public boolean test(RemoteTransition transition) {
+ if (mResult) {
+ mLaunched = true;
+ mTransition = transition;
+ if (mTransition != null) {
+ TransitionInfo info = buildTransitionInfo(mFromActivity, mToActivity);
+ try {
+ transition
+ .getRemoteTransition()
+ .startAnimation(
+ new Binder(),
+ info,
+ new SurfaceControl.Transaction(),
+ new FakeFinishCallback());
+ } catch (RemoteException e) {
+
+ }
+ }
+ }
+ return mResult;
+ }
+
+ @Nullable
+ public RemoteTransition getTransitionOfLastLaunch() {
+ return mTransition;
+ }
+
+ public ComponentName getFromActivity() {
+ return mFromActivity;
+ }
+
+ public ComponentName getToActivity() {
+ return mToActivity;
+ }
+
+ public boolean hasLaunched() {
+ return mLaunched;
+ }
+
+ public void reset() {
+ mTransition = null;
+ mLaunched = false;
+ }
+ }
+
+ private static class FakeIOriginTransitions extends IOriginTransitions.Stub {
+ private final Map<RemoteTransition, RemoteTransition> mRecords = new ArrayMap<>();
+
+ @Override
+ public RemoteTransition makeOriginTransition(
+ RemoteTransition launchTransition, RemoteTransition returnTransition) {
+ mRecords.put(launchTransition, returnTransition);
+ return launchTransition;
+ }
+
+ @Override
+ public void cancelOriginTransition(RemoteTransition originTransition) {
+ mRecords.remove(originTransition);
+ }
+
+ public void runReturnTransition(RemoteTransition originTransition, TransitionInfo info) {
+ RemoteTransition transition = mRecords.remove(originTransition);
+ try {
+ transition
+ .getRemoteTransition()
+ .startAnimation(
+ new Binder(),
+ info,
+ new SurfaceControl.Transaction(),
+ new FakeFinishCallback());
+ } catch (RemoteException e) {
+
+ }
+ }
+
+ public boolean hasPendingReturnTransitions() {
+ return !mRecords.isEmpty();
+ }
+ }
+
+ private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub {
+ @Override
+ public void onTransitionFinished(
+ WindowContainerTransaction wct, SurfaceControl.Transaction sct) {}
+ }
+
+ private static class FakeRemoteTransition extends IRemoteTransition.Stub {
+ private boolean mStarted;
+
+ @Override
+ public void startAnimation(
+ IBinder token,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ mStarted = true;
+ finishCallback.onTransitionFinished(null, null);
+ }
+
+ @Override
+ public void mergeAnimation(
+ IBinder transition,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) {}
+
+ @Override
+ public void takeOverAnimation(
+ IBinder transition,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback,
+ WindowAnimationState[] states) {}
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {}
+
+ public boolean started() {
+ return mStarted;
+ }
+
+ public void reset() {
+ mStarted = false;
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 2c8cc1a..3053672 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -47,7 +47,6 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
-import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -248,35 +247,32 @@
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_removeWhenReorderingAllowedTrue() {
- whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
- val hmp = createHeadsUpManagerPhone()
-
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(notifEntry)
- assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_reorderNotAllowed_seenInShadeTrue() {
+ fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() {
whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
val hmp = createHeadsUpManagerPhone()
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val row = mock<ExpandableNotificationRow>()
+ whenever(row.showingPulsing()).thenReturn(false)
+ notifEntry.row = row
+
hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isTrue();
+ Assert.assertTrue(notifEntry.isSeenInShade)
}
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_reorderAllowed_seenInShadeFalse() {
+ fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() {
whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
val hmp = createHeadsUpManagerPhone()
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val row = mock<ExpandableNotificationRow>()
+ whenever(row.showingPulsing()).thenReturn(false)
+ notifEntry.row = row
+
hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isFalse();
+ Assert.assertFalse(notifEntry.isSeenInShade)
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 6a8cc17..4f3ea83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -126,12 +126,12 @@
private val overlayColorActive =
Utils.applyAlpha(
/* alpha= */ 0.11f,
- Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
)
private val overlayColorInactive =
Utils.applyAlpha(
/* alpha= */ 0.08f,
- Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive),
)
private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
@@ -188,10 +188,7 @@
private var lastState = INVALID
private var lastIconTint = 0
private val launchableViewDelegate =
- LaunchableViewDelegate(
- this,
- superSetVisibility = { super.setVisibility(it) },
- )
+ LaunchableViewDelegate(this, superSetVisibility = { super.setVisibility(it) })
private var lastDisabledByPolicy = false
private val locInScreen = IntArray(2)
@@ -418,7 +415,7 @@
initLongPressEffectCallback()
init(
{ _: View -> longPressEffect.onTileClick() },
- null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
+ { _: View -> true }, // Haptics and long-clicks are handled by [QSLongPressEffect]
)
} else {
val expandable = Expandable.fromView(this)
@@ -583,7 +580,7 @@
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
resources.getString(
R.string.accessibility_tile_disabled_by_policy_action_description
- )
+ ),
)
)
} else {
@@ -591,7 +588,7 @@
info.addAction(
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
- resources.getString(R.string.accessibility_long_click_tile)
+ resources.getString(R.string.accessibility_long_click_tile),
)
)
}
@@ -716,35 +713,35 @@
state.spec,
state.state,
state.disabledByPolicy,
- getBackgroundColorForState(state.state, state.disabledByPolicy)
+ getBackgroundColorForState(state.state, state.disabledByPolicy),
)
if (allowAnimations) {
singleAnimator.setValues(
colorValuesHolder(
BACKGROUND_NAME,
backgroundColor,
- getBackgroundColorForState(state.state, state.disabledByPolicy)
+ getBackgroundColorForState(state.state, state.disabledByPolicy),
),
colorValuesHolder(
LABEL_NAME,
label.currentTextColor,
- getLabelColorForState(state.state, state.disabledByPolicy)
+ getLabelColorForState(state.state, state.disabledByPolicy),
),
colorValuesHolder(
SECONDARY_LABEL_NAME,
secondaryLabel.currentTextColor,
- getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
+ getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
),
colorValuesHolder(
CHEVRON_NAME,
chevronView.imageTintList?.defaultColor ?: 0,
- getChevronColorForState(state.state, state.disabledByPolicy)
+ getChevronColorForState(state.state, state.disabledByPolicy),
),
colorValuesHolder(
OVERLAY_NAME,
backgroundOverlayColor,
- getOverlayColorForState(state.state)
- )
+ getOverlayColorForState(state.state),
+ ),
)
singleAnimator.start()
} else {
@@ -753,7 +750,7 @@
getLabelColorForState(state.state, state.disabledByPolicy),
getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
getChevronColorForState(state.state, state.disabledByPolicy),
- getOverlayColorForState(state.state)
+ getOverlayColorForState(state.state),
)
}
}
@@ -1077,7 +1074,7 @@
backgroundColor,
label.currentTextColor,
secondaryLabel.currentTextColor,
- chevronView.imageTintList?.defaultColor ?: 0
+ chevronView.imageTintList?.defaultColor ?: 0,
)
inner class StateChangeRunnable(private val state: QSTile.State) : Runnable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
index 6907eef..1c840e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
@@ -103,8 +103,7 @@
private boolean mTrackingHeadsUp;
private final HashSet<String> mSwipedOutKeys = new HashSet<>();
private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
- @VisibleForTesting
- public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+ private final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
private boolean mIsShadeOrQsExpanded;
private boolean mIsQsExpanded;
@@ -428,7 +427,7 @@
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeEntry(entry.getKey(), "allowReorder");
+ removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
}
}
mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -631,8 +630,11 @@
super.setEntry(entry, removeRunnable);
if (NotificationThrottleHun.isEnabled()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- if (!mVisualStabilityProvider.isReorderingAllowed()) {
+ if (!mVisualStabilityProvider.isReorderingAllowed()
+ // We don't want to allow reordering while pulsing, but headsup need to
+ // time out anyway
+ && !entry.showingPulsing()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
entry.setSeenInShade(true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index caf09a3..674cbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -46,8 +46,6 @@
private val tag = "AvalancheController"
private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
- var baseEntryMapStr : () -> String = { "baseEntryMapStr not initialized" }
-
var enableAtRuntime = true
set(value) {
if (!value) {
@@ -118,43 +116,32 @@
val key = getKey(entry)
if (runnable == null) {
- headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "Runnable NULL, stop. ${getStateStr()}"
- )
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
return
}
if (!isEnabled) {
- headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "NOT ENABLED, run runnable. ${getStateStr()}"
- )
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
if (entry == null) {
- headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "Entry NULL, stop. ${getStateStr()}"
- )
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
return
}
if (debug) {
debugRunnableLabelMap[runnable] = caller
}
- var stateAfter = ""
+ var outcome = ""
if (isShowing(entry)) {
+ outcome = "update showing"
runnable.run()
- stateAfter = "update showing"
-
} else if (entry in nextMap) {
+ outcome = "update next"
nextMap[entry]?.add(runnable)
- stateAfter = "update next"
-
} else if (headsUpEntryShowing == null) {
+ outcome = "show now"
showNow(entry, arrayListOf(runnable))
- stateAfter = "show now"
-
} else {
// Clean up invalid state when entry is in list but not map and vice versa
if (entry in nextMap) nextMap.remove(entry)
@@ -175,8 +162,8 @@
)
}
}
- stateAfter += getStateStr()
- headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled = true, key, stateAfter)
+ outcome += getStateStr()
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
}
@VisibleForTesting
@@ -194,40 +181,32 @@
val key = getKey(entry)
if (runnable == null) {
- headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled, key,
- "Runnable NULL, stop. ${getStateStr()}"
- )
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
return
}
if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
- headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled = false, key,
- "NOT ENABLED, run runnable. ${getStateStr()}"
- )
return
}
if (entry == null) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "Entry NULL, run runnable")
runnable.run()
- headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled = true, key,
- "Entry NULL, run runnable. ${getStateStr()}"
- )
return
}
- val stateAfter: String
+ val outcome: String
if (entry in nextMap) {
+ outcome = "remove from next"
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
- stateAfter = "remove from next. ${getStateStr()}"
-
} else if (entry in debugDropSet) {
+ outcome = "remove from dropset"
debugDropSet.remove(entry)
- stateAfter = "remove from dropset. ${getStateStr()}"
-
} else if (isShowing(entry)) {
+ outcome = "remove showing"
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -235,13 +214,11 @@
// HUN is animating out, resulting in a flicker.
showNext()
runnable.run()
- stateAfter = "remove showing. ${getStateStr()}"
-
} else {
+ outcome = "run runnable for untracked shown"
runnable.run()
- stateAfter = "run runnable for untracked shown HUN. ${getStateStr()}"
}
- headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), stateAfter)
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
@@ -423,14 +400,12 @@
}
private fun getStateStr(): String {
- return "\nAvalancheController:" +
+ return "\navalanche state:" +
"\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
"\n\tprevious: [$previousHunKey]" +
"\n\tnext list: $nextListStr" +
"\n\tnext map: $nextMapStr" +
- "\n\tdropped: $dropSetStr" +
- "\nBHUM.mHeadsUpEntryMap: " +
- baseEntryMapStr()
+ "\n\tdropped: $dropSetStr"
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 30524a5..f37393a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -116,7 +116,6 @@
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
mAvalancheController = avalancheController;
- mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr);
Resources resources = context.getResources();
mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
@@ -590,18 +589,6 @@
dumpInternal(pw, args);
}
- private String getEntryMapStr() {
- if (mHeadsUpEntryMap.isEmpty()) {
- return "EMPTY";
- }
- StringBuilder entryMapStr = new StringBuilder();
- for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
- entryMapStr.append("\n\t").append(
- entry.mEntry == null ? "null" : entry.mEntry.getKey());
- }
- return entryMapStr.toString();
- }
-
protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
@@ -1005,6 +992,7 @@
* Clear any pending removal runnables.
*/
public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
Runnable runnable = () -> {
final boolean removed = cancelAutoRemovalCallbackInternal();
@@ -1013,7 +1001,6 @@
}
};
if (mEntry != null && isHeadsUpEntry(mEntry.getKey())) {
- mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
mAvalancheController.update(this, runnable, reason + " cancelAutoRemovalCallbacks");
} else {
// Just removed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 41112cb..600270c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -52,7 +52,7 @@
caller: String,
isEnabled: Boolean,
notifEntryKey: String,
- stateAfter: String
+ outcome: String
) {
buffer.log(
TAG,
@@ -60,7 +60,7 @@
{
str1 = caller
str2 = notifEntryKey
- str3 = stateAfter
+ str3 = outcome
bool1 = isEnabled
},
{ "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" }
@@ -71,7 +71,7 @@
caller: String,
isEnabled: Boolean,
notifEntryKey: String,
- stateAfter: String
+ outcome: String
) {
buffer.log(
TAG,
@@ -79,7 +79,7 @@
{
str1 = caller
str2 = notifEntryKey
- str3 = stateAfter
+ str3 = outcome
bool1 = isEnabled
},
{ "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" }
@@ -136,7 +136,7 @@
str1 = entry.logKey
str2 = reason ?: "unknown"
},
- { "$str2 => request: cancelAutoRemovalCallbacks: $str1" }
+ { "request: cancel auto remove of $str1 reason: $str2" }
)
}
@@ -148,7 +148,7 @@
str1 = entry.logKey
str2 = reason ?: "unknown"
},
- { "$str2 => cancel auto remove: $str1" }
+ { "cancel auto remove of $str1 reason: $str2" }
)
}
@@ -161,7 +161,7 @@
str2 = reason
bool1 = isWaiting
},
- { "request: $str2 => removeEntry: $str1 isWaiting: $isWaiting" }
+ { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" }
)
}
@@ -174,7 +174,7 @@
str2 = reason
bool1 = isWaiting
},
- { "$str2 => removeEntry: $str1 isWaiting: $isWaiting" }
+ { "$str2 => remove entry $str1 isWaiting: $isWaiting" }
)
}
@@ -216,12 +216,12 @@
str1 = logKey(key)
str2 = reason
},
- { "remove notif $str1 when headsUpEntry is null, reason: $str2" }
+ { "remove notification $str1 when headsUpEntry is null, reason: $str2" }
)
}
fun logNotificationActuallyRemoved(entry: NotificationEntry) {
- buffer.log(TAG, INFO, { str1 = entry.logKey }, { "removed: $str1 " })
+ buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " })
}
fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
@@ -233,7 +233,7 @@
bool1 = alert
bool2 = hasEntry
},
- { "request: update notif $str1 alert: $bool1 hasEntry: $bool2" }
+ { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }
)
}
@@ -246,7 +246,7 @@
bool1 = alert
bool2 = hasEntry
},
- { "update notif $str1 alert: $bool1 hasEntry: $bool2" }
+ { "update notification $str1 alert: $bool1 hasEntry: $bool2" }
)
}
@@ -281,7 +281,7 @@
bool1 = isPinned
str2 = reason
},
- { "$str2 => setEntryPinned[$bool1]: $str1" }
+ { "$str2 => set entry pinned $str1 pinned: $bool1" }
)
}
@@ -290,7 +290,7 @@
TAG,
INFO,
{ bool1 = hasPinnedNotification },
- { "hasPinnedNotification[$bool1]" }
+ { "has pinned notification changed to $bool1" }
)
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 1e723b5..c8f8c2a 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -204,7 +204,8 @@
serviceIntent,
targetUser,
safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE);
+ /* bindFlags= */ Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE);
})
.exceptionally(
ex -> {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 89dfc73..12e8c57 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -380,10 +380,12 @@
}
/** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+ // TODO: b/262269892 This method was created to perform experiments before the relevant API
+ // was exposed. Now it is obsolete and should be removed.
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Set<Integer> getRestrictedTransportsFromCarrierConfig(
ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
- if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
+ if (!Build.isDebuggable()) {
return RESTRICTED_TRANSPORTS_DEFAULT;
}
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
index b271d7e..ab69cd1 100644
--- a/services/core/java/com/android/server/WiredAccessoryManager.java
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -118,8 +118,11 @@
if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_LINEOUT_INSERT) == 1) {
switchValues |= SW_LINEOUT_INSERT_BIT;
}
- notifyWiredAccessoryChanged(0, switchValues,
- SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT);
+ notifyWiredAccessoryChanged(
+ 0,
+ switchValues,
+ SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT,
+ true /*isSynchronous*/);
}
@@ -135,7 +138,13 @@
}
@Override
- public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
+ public void notifyWiredAccessoryChanged(
+ long whenNanos, int switchValues, int switchMask) {
+ notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask, false /*isSynchronous*/);
+ }
+
+ public void notifyWiredAccessoryChanged(
+ long whenNanos, int switchValues, int switchMask, boolean isSynchronous) {
if (LOG) {
Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos
+ " bits=" + switchCodeToString(switchValues, switchMask)
@@ -172,8 +181,10 @@
break;
}
- updateLocked(NAME_H2W,
- (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
+ updateLocked(
+ NAME_H2W,
+ (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset,
+ isSynchronous);
}
}
@@ -195,8 +206,9 @@
*
* @param newName One of the NAME_xxx variables defined above.
* @param newState 0 or one of the BIT_xxx variables defined above.
+ * @param isSynchronous boolean to determine whether should happen sync or async
*/
- private void updateLocked(String newName, int newState) {
+ private void updateLocked(String newName, int newState, boolean isSynchronous) {
// Retain only relevant bits
int headsetState = newState & SUPPORTED_HEADSETS;
int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
@@ -234,12 +246,15 @@
return;
}
- mWakeLock.acquire();
-
- Log.i(TAG, "MSG_NEW_DEVICE_STATE");
- Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
- mHeadsetState, "");
- mHandler.sendMessage(msg);
+ if (isSynchronous) {
+ setDevicesState(headsetState, mHeadsetState, "");
+ } else {
+ mWakeLock.acquire();
+ Log.i(TAG, "MSG_NEW_DEVICE_STATE");
+ Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
+ mHeadsetState, "");
+ mHandler.sendMessage(msg);
+ }
mHeadsetState = headsetState;
}
@@ -439,7 +454,10 @@
for (int i = 0; i < mUEventInfo.size(); ++i) {
UEventInfo uei = mUEventInfo.get(i);
if (devPath.equals(uei.getDevPath())) {
- updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state));
+ updateLocked(
+ name,
+ uei.computeNewHeadsetState(mHeadsetState, state),
+ false /*isSynchronous*/);
return;
}
}
@@ -550,7 +568,10 @@
synchronized (mLock) {
int mask = maskAndState.first;
int state = maskAndState.second;
- updateLocked(name, mHeadsetState & ~(mask & ~state) | (mask & state));
+ updateLocked(
+ name,
+ mHeadsetState & ~(mask & ~state) | (mask & state),
+ false /*isSynchronous*/);
return;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a21a3f1..35323d6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -579,7 +579,7 @@
static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
// How many seconds should the system wait before terminating the spawned logcat process.
- static final int LOGCAT_TIMEOUT_SEC = 10;
+ static final int LOGCAT_TIMEOUT_SEC = Flags.logcatLongerTimeout() ? 15 : 10;
// Necessary ApplicationInfo flags to mark an app as persistent
static final int PERSISTENT_MASK =
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 75e9fad..15277ce 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -437,7 +437,6 @@
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
mPowerAttributor, mPowerProfile, mCpuScalingPolicies,
mPowerStatsStore, Clock.SYSTEM_CLOCK);
- mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore);
mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
@@ -504,6 +503,9 @@
}
public void systemServicesReady() {
+ mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore,
+ Flags.accumulateBatteryUsageStats());
+
MultiStatePowerAttributor attributor = (MultiStatePowerAttributor) mPowerAttributor;
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU,
Flags.streamlinedBatteryStats());
@@ -1100,14 +1102,17 @@
DEVICE_CONFIG_NAMESPACE,
MIN_CONSUMED_POWER_THRESHOLD_KEY,
0);
- final BatteryUsageStatsQuery query =
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includeVirtualUids()
- .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
- .build();
- bus = getBatteryUsageStats(List.of(query)).get(0);
+ BatteryUsageStatsQuery.Builder query = new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold);
+
+ if (Flags.accumulateBatteryUsageStats()) {
+ query.accumulated();
+ }
+
+ bus = getBatteryUsageStats(List.of(query.build())).get(0);
final int pullResult =
new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
try {
@@ -3016,6 +3021,9 @@
if (Flags.streamlinedBatteryStats()) {
pw.println(" --sample: collect and dump a sample of stats for debugging purpose");
}
+ if (Flags.accumulateBatteryUsageStats()) {
+ pw.println(" --accumulated: continuously accumulated since setup or reset-all");
+ }
pw.println(" <package.name>: optional name of package to filter output by.");
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
@@ -3083,7 +3091,7 @@
}
private void dumpUsageStats(FileDescriptor fd, PrintWriter pw, int model,
- boolean proto) {
+ boolean proto, boolean accumulated) {
awaitCompletion();
syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
@@ -3097,6 +3105,9 @@
if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
builder.powerProfileModeledOnly();
}
+ if (accumulated) {
+ builder.accumulated();
+ }
BatteryUsageStatsQuery query = builder.build();
synchronized (mStats) {
mStats.prepareForDumpLocked();
@@ -3287,6 +3298,7 @@
} else if ("--usage".equals(arg)) {
int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
boolean proto = false;
+ boolean accumulated = false;
for (int j = i + 1; j < args.length; j++) {
switch (args[j]) {
case "--proto":
@@ -3309,9 +3321,12 @@
}
break;
}
+ case "--accumulated":
+ accumulated = true;
+ break;
}
}
- dumpUsageStats(fd, pw, model, proto);
+ dumpUsageStats(fd, pw, model, proto, accumulated);
return;
} else if ("--wakeups".equals(arg)) {
mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "),
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 3334393..9b51b6a 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -194,4 +194,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "logcat_longer_timeout"
+ namespace: "backstage_power"
+ description: "Wait longer during the logcat gathering operation"
+ bug: "292533246"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index ce92dfb..b421264 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -605,7 +605,11 @@
break;
case BluetoothProfile.LE_AUDIO:
if (mLeAudio != null && mLeAudioCallback != null) {
- mLeAudio.unregisterCallback(mLeAudioCallback);
+ try {
+ mLeAudio.unregisterCallback(mLeAudioCallback);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while unregistering callback for LE audio", e);
+ }
}
mLeAudio = null;
mLeAudioCallback = null;
@@ -682,12 +686,21 @@
return;
}
if (mLeAudio != null && mLeAudioCallback != null) {
- mLeAudio.unregisterCallback(mLeAudioCallback);
+ try {
+ mLeAudio.unregisterCallback(mLeAudioCallback);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while unregistering callback for LE audio", e);
+ }
}
mLeAudio = (BluetoothLeAudio) proxy;
mLeAudioCallback = new MyLeAudioCallback();
- mLeAudio.registerCallback(
- mContext.getMainExecutor(), mLeAudioCallback);
+ try{
+ mLeAudio.registerCallback(
+ mContext.getMainExecutor(), mLeAudioCallback);
+ } catch (Exception e) {
+ mLeAudioCallback = null;
+ Log.e(TAG, "Exception while registering callback for LE audio", e);
+ }
break;
case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.LE_AUDIO_BROADCAST:
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index a1e5ebc..cf0c5b0 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -180,7 +180,8 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) {
+ if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
+ || event.getClassification() == MotionEvent.CLASSIFICATION_PINCH) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2bb2b7b..f0fb33e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4799,7 +4799,8 @@
userData.mImeBindingState.mFocusedWindowEditorInfo,
info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode,
reason, userData.mInFullscreenMode, info.requestWindowName,
- info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName));
+ info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName,
+ userId));
if (statsToken != null) {
mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -6132,8 +6133,7 @@
dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
}
- // TODO(b/365868861): Make StartInputHistory, SoftInputShowHideHistory and ImeTracker per
- // user.
+ // TODO(b/365868861): Make StartInputHistory and ImeTracker multi-user aware.
synchronized (ImfLock.class) {
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
index 3023603..8445632 100644
--- a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.SystemClock;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -62,6 +63,8 @@
final String mImeTargetNameFromWm;
@Nullable
final String mImeSurfaceParentName;
+ @UserIdInt
+ final int mImeUserId;
Entry(ClientState client, EditorInfo editorInfo,
String focusedWindowName,
@@ -69,7 +72,7 @@
@SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
@Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
+ @Nullable String imeSurfaceParentName, @UserIdInt int imeUserId) {
mClientState = client;
mEditorInfo = editorInfo;
mFocusedWindowName = focusedWindowName;
@@ -82,6 +85,7 @@
mImeControlTargetName = imeControlTargetName;
mImeTargetNameFromWm = imeTargetName;
mImeSurfaceParentName = imeSurfaceParentName;
+ mImeUserId = imeUserId;
}
}
@@ -102,7 +106,8 @@
continue;
}
pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+ pw.println("SoftInputShowHide[" + entry.mImeUserId + "] #"
+ + entry.mSequenceNumber + ":");
pw.print(prefix);
pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index f6d9dc2..03a34f2 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -87,6 +87,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
import java.io.FileDescriptor;
@@ -194,9 +195,13 @@
mIsServiceEnabled = isServiceEnabled();
}
+ }
- //connect to remote services(if available) during boot phase.
- if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle());
+ //connect to remote services(if available) during boot.
+ if(user.getUserHandle().equals(UserHandle.SYSTEM)) {
try {
ensureRemoteInferenceServiceInitialized();
ensureRemoteIntelligenceServiceInitialized();
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 4665a72..89ced12 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2208,10 +2208,10 @@
return true;
}
boolean permissionGranted = requireFullPermission ? hasPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
: (hasPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
if (!permissionGranted) {
if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5653da0..8657de2 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -716,7 +716,7 @@
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
- mPackageManagerInternal.getInstalledApplications(
+ mPackageManagerInternal.getInstalledApplicationsCrossUser(
/* flags= */ 0, user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ff9c3e5..611e0d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3389,18 +3389,31 @@
return true;
}
// Does it contain a device admin for any user?
- int[] users;
+ int[] allUsers = mUserManager.getUserIds();
+ int[] targetUsers;
if (userId == UserHandle.USER_ALL) {
- users = mUserManager.getUserIds();
+ targetUsers = allUsers;
} else {
- users = new int[]{userId};
+ targetUsers = new int[]{userId};
}
- for (int i = 0; i < users.length; ++i) {
- if (dpm.packageHasActiveAdmins(packageName, users[i])) {
+
+ for (int i = 0; i < targetUsers.length; ++i) {
+ if (dpm.packageHasActiveAdmins(packageName, targetUsers[i])) {
return true;
}
- if (isDeviceManagementRoleHolder(packageName, users[i])
- && dpmi.isUserOrganizationManaged(users[i])) {
+ }
+
+ // If a package is DMRH on a managed user, it should also be treated as an admin on
+ // that user. If that package is also a system package, it should also be protected
+ // on other users otherwise "uninstall updates" on an unmanaged user may break
+ // management on other users because apk version is shared between all users.
+ var packageState = snapshotComputer().getPackageStateInternal(packageName);
+ if (packageState == null) {
+ return false;
+ }
+ for (int user : packageState.isSystem() ? allUsers : targetUsers) {
+ if (isDeviceManagementRoleHolder(packageName, user)
+ && dpmi.isUserOrganizationManaged(user)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
new file mode 100644
index 0000000..dd6d5db
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AccumulatedBatteryUsageStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "accumulated-battery-usage-stats";
+ public static final long ID = Long.MAX_VALUE;
+
+ private final BatteryUsageStats.Builder mBatteryUsageStats;
+
+ AccumulatedBatteryUsageStatsSection(BatteryUsageStats.Builder batteryUsageStats) {
+ super(TYPE);
+ mBatteryUsageStats = batteryUsageStats;
+ }
+
+ public BatteryUsageStats.Builder getBatteryUsageStatsBuilder() {
+ return mBatteryUsageStats;
+ }
+
+ @Override
+ public void write(TypedXmlSerializer serializer) throws IOException {
+ mBatteryUsageStats.build().writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mBatteryUsageStats.build().dump(ipw, "");
+ }
+
+ static class Reader implements PowerStatsSpan.SectionReader {
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new AccumulatedBatteryUsageStatsSection(
+ BatteryUsageStats.createBuilderFromXml(parser));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index cb8e1a0..3f1d9a3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -509,6 +509,7 @@
}
private boolean mSaveBatteryUsageStatsOnReset;
+ private boolean mAccumulateBatteryUsageStats;
private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
private PowerStatsStore mPowerStatsStore;
@@ -11975,10 +11976,12 @@
*/
public void saveBatteryUsageStatsOnReset(
@NonNull BatteryUsageStatsProvider batteryUsageStatsProvider,
- @NonNull PowerStatsStore powerStatsStore) {
+ @NonNull PowerStatsStore powerStatsStore,
+ boolean accumulateBatteryUsageStats) {
mSaveBatteryUsageStatsOnReset = true;
mBatteryUsageStatsProvider = batteryUsageStatsProvider;
mPowerStatsStore = powerStatsStore;
+ mAccumulateBatteryUsageStats = accumulateBatteryUsageStats;
}
@GuardedBy("this")
@@ -12179,29 +12182,33 @@
return;
}
- final BatteryUsageStats batteryUsageStats;
- synchronized (this) {
- batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this,
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .includeProcessStateData()
- .build());
- }
-
- // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
- // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
- // start time
- long monotonicStartTime =
- mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
- mHandler.post(() -> {
- mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats);
- try {
- batteryUsageStats.close();
- } catch (IOException e) {
- Log.e(TAG, "Cannot close BatteryUsageStats", e);
+ if (mAccumulateBatteryUsageStats) {
+ mBatteryUsageStatsProvider.accumulateBatteryUsageStats(this);
+ } else {
+ final BatteryUsageStats batteryUsageStats;
+ synchronized (this) {
+ batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this,
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerModels()
+ .includeProcessStateData()
+ .build());
}
- });
+
+ // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+ // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+ // start time
+ long monotonicStartTime =
+ mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+ mHandler.post(() -> {
+ mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats);
+ try {
+ batteryUsageStats.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot close BatteryUsageStats", e);
+ }
+ });
+ }
}
@GuardedBy("this")
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 87a3e5e..d66e05b 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -33,6 +33,7 @@
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -64,6 +65,7 @@
mClock = clock;
mPowerStatsStore.addSectionReader(new BatteryUsageStatsSection.Reader());
+ mPowerStatsStore.addSectionReader(new AccumulatedBatteryUsageStatsSection.Reader());
}
private List<PowerCalculator> getPowerCalculators() {
@@ -151,6 +153,56 @@
}
/**
+ * Compute BatteryUsageStats for the period since the last accumulated stats were stored,
+ * add them to the accumulated stats and save the result.
+ */
+ public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
+ BatteryUsageStats.Builder accumulatedBatteryUsageStatsBuilder = null;
+
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ AccumulatedBatteryUsageStatsSection.ID,
+ AccumulatedBatteryUsageStatsSection.TYPE);
+ if (powerStatsSpan != null) {
+ List<PowerStatsSpan.Section> sections = powerStatsSpan.getSections();
+ for (int i = sections.size() - 1; i >= 0; i--) {
+ PowerStatsSpan.Section section = sections.get(i);
+ if (AccumulatedBatteryUsageStatsSection.TYPE.equals(section.getType())) {
+ accumulatedBatteryUsageStatsBuilder =
+ ((AccumulatedBatteryUsageStatsSection) section)
+ .getBatteryUsageStatsBuilder();
+ break;
+ }
+ }
+ }
+
+ // TODO(b/366493365): add the current batteryusagestats directly into the "accumulated"
+ // builder to avoid allocating a second CursorWindow
+ BatteryUsageStats.Builder currentBatteryUsageStatsBuilder =
+ getCurrentBatteryUsageStatsBuilder(stats,
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includePowerStateData()
+ .includeScreenStateData()
+ .build(),
+ mClock.currentTimeMillis());
+
+ if (accumulatedBatteryUsageStatsBuilder == null) {
+ accumulatedBatteryUsageStatsBuilder = currentBatteryUsageStatsBuilder;
+ } else {
+ accumulatedBatteryUsageStatsBuilder.add(currentBatteryUsageStatsBuilder.build());
+ currentBatteryUsageStatsBuilder.discard();
+ }
+
+ powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
+ powerStatsSpan.addSection(
+ new AccumulatedBatteryUsageStatsSection(accumulatedBatteryUsageStatsBuilder));
+
+ mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
+ accumulatedBatteryUsageStatsBuilder::discard);
+ }
+
+ /**
* Returns true if the last update was too long ago for the tolerances specified
* by the supplied queries.
*/
@@ -192,15 +244,67 @@
private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
- if (query.getToTimestamp() == 0) {
+ if ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
+ return getAccumulatedBatteryUsageStats(stats, query);
+ } else if (query.getToTimestamp() == 0) {
return getCurrentBatteryUsageStats(stats, query, currentTimeMs);
} else {
return getAggregatedBatteryUsageStats(stats, query);
}
}
+ private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
+ BatteryUsageStatsQuery query) {
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ AccumulatedBatteryUsageStatsSection.ID,
+ AccumulatedBatteryUsageStatsSection.TYPE);
+
+ BatteryUsageStats.Builder accumulatedBatteryUsageStatsBuilder = null;
+ if (powerStatsSpan != null) {
+ List<PowerStatsSpan.Section> sections = powerStatsSpan.getSections();
+ if (sections.size() == 1) {
+ accumulatedBatteryUsageStatsBuilder =
+ ((AccumulatedBatteryUsageStatsSection) sections.get(0))
+ .getBatteryUsageStatsBuilder();
+ } else {
+ Slog.wtf(TAG, "Unexpected number of sections for type "
+ + AccumulatedBatteryUsageStatsSection.TYPE);
+ }
+ }
+
+ BatteryUsageStats currentBatteryUsageStats = getCurrentBatteryUsageStats(stats, query,
+ mClock.currentTimeMillis());
+
+ BatteryUsageStats result;
+ if (accumulatedBatteryUsageStatsBuilder == null) {
+ result = currentBatteryUsageStats;
+ } else {
+ accumulatedBatteryUsageStatsBuilder.add(currentBatteryUsageStats);
+ try {
+ currentBatteryUsageStats.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Closing BatteryUsageStats", ex);
+ }
+ result = accumulatedBatteryUsageStatsBuilder.build();
+ }
+
+ return result;
+ }
+
private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
+ BatteryUsageStats.Builder builder = getCurrentBatteryUsageStatsBuilder(stats, query,
+ currentTimeMs);
+ BatteryUsageStats batteryUsageStats = builder.build();
+ if (batteryUsageStats.isProcessStateDataIncluded()) {
+ verify(batteryUsageStats);
+ }
+ return batteryUsageStats;
+ }
+
+ private BatteryUsageStats.Builder getCurrentBatteryUsageStatsBuilder(BatteryStatsImpl stats,
+ BatteryUsageStatsQuery query, long currentTimeMs) {
final long realtimeUs = mClock.elapsedRealtime() * 1000;
final long uptimeUs = mClock.uptimeMillis() * 1000;
@@ -274,11 +378,7 @@
mPowerAttributor.estimatePowerConsumption(batteryUsageStatsBuilder, stats.getHistory(),
monotonicStartTime, monotonicEndTime);
- BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
- if (includeProcessStateData) {
- verify(batteryUsageStats);
- }
- return batteryUsageStats;
+ return batteryUsageStatsBuilder;
}
// STOPSHIP(b/229906525): remove verification before shipping
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
index a875c30..5a6f973 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -133,6 +133,19 @@
}
/**
+ * Schedules saving the specified span on the background thread.
+ */
+ public void storePowerStatsSpanAsync(PowerStatsSpan span, Runnable onComplete) {
+ mHandler.post(() -> {
+ try {
+ storePowerStatsSpan(span);
+ } finally {
+ onComplete.run();
+ }
+ });
+ }
+
+ /**
* Saves the specified span in the store.
*/
public void storePowerStatsSpan(PowerStatsSpan span) {
@@ -172,6 +185,9 @@
lockStoreDirectory();
try {
File file = makePowerStatsSpanFilename(id);
+ if (!file.exists()) {
+ return null;
+ }
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return PowerStatsSpan.read(inputStream, parser, mSectionReaders, sectionTypes);
} catch (IOException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 05d29f5..ce6f57f 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -76,3 +76,13 @@
bug: "364350206"
is_fixed_read_only: true
}
+
+flag {
+ name: "accumulate_battery_usage_stats"
+ namespace: "backstage_power"
+ description: "Add support for monotonically accumulated BatteryUsageStats"
+ bug: "345022340"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 090e679..5b22c10 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.PowerManager;
@@ -117,7 +119,7 @@
* before the release callback.
*/
boolean runVibrationOnVibrationThread(VibrationStepConductor conductor) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrationOnVibrationThread");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "runVibrationOnVibrationThread");
try {
synchronized (mLock) {
if (mRequestedActiveConductor != null) {
@@ -129,7 +131,7 @@
}
return true;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -249,17 +251,17 @@
private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "notifyVibrationComplete");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "notifyVibrationComplete");
try {
mExecutingConductor.notifyVibrationComplete(vibrationEndInfo);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
private void playVibration() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "playVibration");
try {
mExecutingConductor.prepareToStart();
while (!mExecutingConductor.isFinished()) {
@@ -283,7 +285,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 3c47850..c120fc7 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
+
import android.annotation.Nullable;
import android.hardware.vibrator.IVibrator;
import android.os.Binder;
@@ -124,7 +126,7 @@
/** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */
public void reloadVibratorInfoIfNeeded() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#reloadVibratorInfoIfNeeded");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#reloadVibratorInfoIfNeeded");
try {
// Early check outside lock, for quick return.
if (mVibratorInfoLoadSuccessful) {
@@ -143,7 +145,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -199,13 +201,13 @@
/** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
public boolean isAvailable() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#isAvailable");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#isAvailable");
try {
synchronized (mLock) {
return mNativeWrapper.isAvailable();
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -215,7 +217,9 @@
* <p>This will affect the state of {@link #isUnderExternalControl()}.
*/
public void setExternalControl(boolean externalControl) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setExternalControl(" + externalControl + ")");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR,
+ externalControl ? "VibratorController#enableExternalControl"
+ : "VibratorController#disableExternalControl");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
return;
@@ -225,7 +229,7 @@
mNativeWrapper.setExternalControl(externalControl);
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -234,7 +238,7 @@
* if given {@code effect} is {@code null}.
*/
public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#updateAlwaysOn");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#updateAlwaysOn");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
return;
@@ -248,13 +252,13 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
/** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
public void setAmplitude(float amplitude) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#setAmplitude");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#setAmplitude");
try {
synchronized (mLock) {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
@@ -265,7 +269,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -279,7 +283,7 @@
* do not support the input or a negative number if the operation failed.
*/
public long on(long milliseconds, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on");
try {
synchronized (mLock) {
long duration = mNativeWrapper.on(milliseconds, vibrationId);
@@ -290,7 +294,7 @@
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -304,7 +308,7 @@
* do not support the input or a negative number if the operation failed.
*/
public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
synchronized (mLock) {
Parcel vendorData = Parcel.obtain();
try {
@@ -320,7 +324,7 @@
return duration;
} finally {
vendorData.recycle();
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
@@ -335,7 +339,7 @@
* do not support the input or a negative number if the operation failed.
*/
public long on(PrebakedSegment prebaked, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
try {
synchronized (mLock) {
long duration = mNativeWrapper.perform(prebaked.getEffectId(),
@@ -347,7 +351,7 @@
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -361,7 +365,7 @@
* do not support the input or a negative number if the operation failed.
*/
public long on(PrimitiveSegment[] primitives, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
@@ -375,7 +379,7 @@
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -388,7 +392,7 @@
* @return The duration of the effect playing, or 0 if unsupported.
*/
public long on(RampSegment[] primitives, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
return 0;
@@ -403,7 +407,7 @@
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -413,7 +417,7 @@
* <p>This will affect the state of {@link #isVibrating()}.
*/
public void off() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#off");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#off");
try {
synchronized (mLock) {
mNativeWrapper.off();
@@ -421,7 +425,7 @@
notifyListenerOnVibrating(false);
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index bd17e5d..95c6483 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
import static android.os.VibrationAttributes.USAGE_CLASS_ALARM;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -333,7 +334,7 @@
@VisibleForTesting
void systemReady() {
Slog.v(TAG, "Initializing VibratorManager service...");
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "systemReady");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "systemReady");
try {
// Will retry to load each vibrator's info, if any request have failed.
for (int i = 0; i < mVibrators.size(); i++) {
@@ -352,7 +353,7 @@
mServiceReady = true;
}
Slog.v(TAG, "VibratorManager service initialized");
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -413,7 +414,7 @@
@Override // Binder call
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
@Nullable CombinedVibration effect, @Nullable VibrationAttributes attrs) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE_ALWAYS_ON,
@@ -449,20 +450,25 @@
}
return true;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override // Binder call
public void vibrate(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
- vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "vibrate");
+ try {
+ vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
}
@Override // Binder call
public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
String reason, int flags, int privFlags) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedback");
// Note that the `performHapticFeedback` method does not take a token argument from the
// caller, and instead, uses this service as the token. This is to mitigate performance
// impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
@@ -471,7 +477,7 @@
performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
this, flags, privFlags);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -479,13 +485,13 @@
public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
int constant, int inputDeviceId, int inputSource, String reason, int flags,
int privFlags) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
try {
performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant,
inputDeviceId,
inputSource, reason, /* token= */ this, flags, privFlags);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -563,26 +569,16 @@
HalVibration vibrateWithPermissionCheck(int uid, int deviceId, String opPkg,
@NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
- try {
- attrs = fixupVibrationAttributes(attrs, effect);
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.VIBRATE, "vibrate");
- return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
+ attrs = fixupVibrationAttributes(attrs, effect);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE, "vibrate");
+ return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
}
HalVibration vibrateWithoutPermissionCheck(int uid, int deviceId, String opPkg,
@NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
- try {
- return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
+ return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
}
private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
@@ -671,7 +667,7 @@
@Override // Binder call
public void cancelVibrate(int usageFilter, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "cancelVibrate");
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE,
@@ -708,7 +704,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -899,7 +895,7 @@
@GuardedBy("mLock")
@Nullable
private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
if (mInputDeviceDelegate.isAvailable()) {
return startVibrationOnInputDevicesLocked(vib);
@@ -919,7 +915,7 @@
mNextVibration = conductor;
return null;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -930,7 +926,7 @@
int mode = startAppOpModeLocked(vib.callerInfo);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
mCurrentVibration = conductor;
if (!mCurrentVibration.linkToDeath()) {
@@ -1581,7 +1577,7 @@
@Override
public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "prepareSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "prepareSyncedVibration");
try {
if ((mCapabilities & requiredCapabilities) != requiredCapabilities) {
// This sync step requires capabilities this device doesn't have, skipping
@@ -1590,33 +1586,33 @@
}
return mNativeWrapper.prepareSynced(vibratorIds);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public boolean triggerSyncedVibration(long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "triggerSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "triggerSyncedVibration");
try {
return mNativeWrapper.triggerSynced(vibrationId);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void cancelSyncedVibration() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "cancelSyncedVibration");
try {
mNativeWrapper.cancelSynced();
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void noteVibratorOn(int uid, long duration) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOn");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "noteVibratorOn");
try {
if (duration <= 0) {
// Tried to turn vibrator ON and got:
@@ -1635,20 +1631,20 @@
} catch (RemoteException e) {
Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void noteVibratorOff(int uid) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOff");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "noteVibratorOff");
try {
mBatteryStatsService.noteVibratorOff(uid);
mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
} catch (RemoteException e) {
Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -1657,7 +1653,8 @@
if (DEBUG) {
Slog.d(TAG, "VibrationThread released after finished vibration");
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onVibrationThreadReleased: " + vibrationId);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
+
try {
synchronized (mLock) {
if (DEBUG) {
@@ -1686,7 +1683,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
@@ -1994,7 +1991,7 @@
@Override
public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStart");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStart");
try {
// Create Vibration.Stats as close to the received request as possible, for
// tracking.
@@ -2116,13 +2113,13 @@
return externalVibration.getScale();
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void onExternalVibrationStop(ExternalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
try {
synchronized (mLock) {
if (mCurrentExternalVibration != null
@@ -2135,7 +2132,7 @@
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -2203,32 +2200,39 @@
@Override
public int onCommand(String cmd) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
try {
if ("list".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: list");
return runListVibrators();
}
if ("synced".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: synced");
return runMono();
}
if ("combined".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: combined");
return runStereo();
}
if ("sequential".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: sequential");
return runSequential();
}
if ("xml".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: xml");
return runXml();
}
if ("cancel".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: cancel");
return runCancel();
}
if ("feedback".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: feedback");
return runHapticFeedback();
}
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: default");
return handleDefaultCommands(cmd);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 407a5a6..b6a4481 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19384,11 +19384,13 @@
PolicyDefinition.RESET_PASSWORD_TOKEN,
enforcingAdmin,
userId);
- // TODO(b/369152176): Address difference in behavior regarding addEscrowToken when
- // compared with the else branch.
long tokenHandle = addEscrowToken(
token, currentTokenHandle == null ? 0 : currentTokenHandle, userId);
if (tokenHandle == 0) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
return false;
}
mDevicePolicyEngine.setLocalPolicy(
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
new file mode 100644
index 0000000..fead05b
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.supervision;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Bundle;
+
+/**
+ * Local system service interface for {@link SupervisionService}.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class SupervisionManagerInternal {
+ /**
+ * Returns whether supervision is enabled for the specified user
+ *
+ * @param userId The user to retrieve the supervision state for
+ * @return whether the user is supervised
+ */
+ public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
+
+ /**
+ * Sets whether the supervision lock screen should be shown for the specified user
+ *
+ * @param userId The user set the superivision state for
+ * @param enabled Whether or not the superivision lock screen needs to be shown
+ * @param options Optional configuration parameters for the supervision lock screen
+ */
+ public abstract void setSupervisionLockscreenEnabledForUser(
+ @UserIdInt int userId, boolean enabled, @Nullable Bundle options);
+}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 7ffd0ec..4c515c1 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -18,14 +18,22 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.supervision.ISupervisionManager;
import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -38,13 +46,25 @@
private final Context mContext;
+ // TODO(b/362756788): Does this need to be a LockGuard lock?
+ private final Object mLockDoNoUseDirectly = new Object();
+
+ @GuardedBy("getLockObject()")
+ private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
+
+ private final UserManagerInternal mUserManagerInternal;
+
public SupervisionService(Context context) {
- mContext = context.createAttributionContext("SupervisionService");
+ mContext = context.createAttributionContext(LOG_TAG);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
}
@Override
- public boolean isSupervisionEnabled() {
- return false;
+ public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionEnabled;
+ }
}
@Override
@@ -60,11 +80,44 @@
}
@Override
- protected void dump(
- @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
+ protected void dump(@NonNull FileDescriptor fd,
+ @NonNull PrintWriter printWriter, @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return;
- fout.println("Supervision enabled: " + isSupervisionEnabled());
+ try (var pw = new IndentingPrintWriter(printWriter, " ")) {
+ pw.println("SupervisionService state:");
+ pw.increaseIndent();
+
+ var users = mUserManagerInternal.getUsers(false);
+ synchronized (getLockObject()) {
+ for (var user : users) {
+ getUserDataLocked(user.id).dump(pw);
+ pw.println();
+ }
+ }
+ }
+ }
+
+ private Object getLockObject() {
+ return mLockDoNoUseDirectly;
+ }
+
+ @NonNull
+ @GuardedBy("getLockObject()")
+ SupervisionUserData getUserDataLocked(@UserIdInt int userId) {
+ SupervisionUserData data = mUserData.get(userId);
+ if (data == null) {
+ // TODO(b/362790738): Do not create user data for nonexistent users.
+ data = new SupervisionUserData(userId);
+ mUserData.append(userId, data);
+ }
+ return data;
+ }
+
+ void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ synchronized (getLockObject()) {
+ getUserDataLocked(userId).supervisionEnabled = enabled;
+ }
}
public static class Lifecycle extends SystemService {
@@ -77,7 +130,35 @@
@Override
public void onStart() {
+ publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
}
}
+
+ final SupervisionManagerInternal mInternal = new SupervisionManagerInternal() {
+ public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionEnabled;
+ }
+ }
+
+ @Override
+ public void setSupervisionLockscreenEnabledForUser(
+ @UserIdInt int userId, boolean enabled, @Nullable Bundle options) {
+ synchronized (getLockObject()) {
+ SupervisionUserData data = getUserDataLocked(userId);
+ data.supervisionLockScreenEnabled = enabled;
+ data.supervisionLockScreenOptions = options;
+ }
+ }
+ };
+
+ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ synchronized (getLockObject()) {
+ mUserData.remove(user.id);
+ }
+ }
+ }
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
index 3aba24a..2adaae3 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
@@ -17,8 +17,7 @@
package com.android.server.supervision;
import android.os.ShellCommand;
-
-import java.io.PrintWriter;
+import android.os.UserHandle;
public class SupervisionServiceShellCommand extends ShellCommand {
private final SupervisionService mService;
@@ -32,30 +31,29 @@
if (cmd == null) {
return handleDefaultCommands(null);
}
- final PrintWriter pw = getOutPrintWriter();
switch (cmd) {
- case "help": return help(pw);
- case "is-enabled": return isEnabled(pw);
+ case "enable": return setEnabled(true);
+ case "disable": return setEnabled(false);
default: return handleDefaultCommands(cmd);
}
}
- private int help(PrintWriter pw) {
- pw.println("Supervision service commands:");
- pw.println(" help");
- pw.println(" Prints this help text");
- pw.println(" is-enabled");
- pw.println(" Is supervision enabled");
- return 0;
- }
-
- private int isEnabled(PrintWriter pw) {
- pw.println(mService.isSupervisionEnabled());
+ private int setEnabled(boolean enabled) {
+ final var pw = getOutPrintWriter();
+ final var userId = UserHandle.parseUserArg(getNextArgRequired());
+ mService.setSupervisionEnabledForUser(userId, enabled);
return 0;
}
@Override
public void onHelp() {
- help(getOutPrintWriter());
+ final var pw = getOutPrintWriter();
+ pw.println("Supervision service (supervision) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text");
+ pw.println(" enable <USER_ID>");
+ pw.println(" Enables supervision for the given user.");
+ pw.println(" disable <USER_ID>");
+ pw.println(" Disables supervision for the given user.");
}
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
new file mode 100644
index 0000000..5616237
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.supervision;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Bundle;
+import android.util.IndentingPrintWriter;
+
+/** User specific data, used internally by the {@link SupervisionService}. */
+public class SupervisionUserData {
+ public final @UserIdInt int userId;
+ public boolean supervisionEnabled;
+ public boolean supervisionLockScreenEnabled;
+ @Nullable public Bundle supervisionLockScreenOptions;
+
+ public SupervisionUserData(@UserIdInt int userId) {
+ this.userId = userId;
+ }
+
+ void dump(@NonNull IndentingPrintWriter pw) {
+ pw.println();
+ pw.println("User " + userId + ":");
+ pw.increaseIndent();
+ pw.println("supervisionEnabled: " + supervisionEnabled);
+ pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled);
+ pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions);
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index e5d3153..72cbac3 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -35,6 +35,7 @@
public final class InputMethodManagerServiceTests {
static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
+ private static final int TEST_IME_USER_ID = 1;
static InputMethodManagerService.ImeDisplayValidator sChecker =
(displayId) -> {
@@ -102,7 +103,8 @@
null,
null,
null,
- null));
+ null,
+ TEST_IME_USER_ID));
history.dump(new PrintWriter(writer), "" /* prefix */);
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 5c4716d..7d5532f 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,6 +57,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doReturn
@@ -383,6 +384,10 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
PackageManager.PERMISSION_GRANTED
}
+ whenever(this.checkPermission(
+ eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
+ PackageManager.PERMISSION_GRANTED
+ }
}
val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 1d20538..c037f97 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -927,7 +927,7 @@
assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider,
- mPowerStatsStore);
+ mPowerStatsStore, /* accumulateBatteryUsageStats */ false);
synchronized (mBatteryStatsImpl) {
mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index fde84e9..0e60156 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -419,7 +419,8 @@
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
- batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+ batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
+ /* accumulateBatteryUsageStats */ false);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -505,6 +506,102 @@
.of(180.0);
}
+ @Test
+ public void accumulateBatteryUsageStats() {
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ setTime(5 * MINUTE_IN_MS);
+
+ // Capture the session start timestamp
+ synchronized (batteryStats) {
+ batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ PowerStatsStore powerStatsStore = new PowerStatsStore(
+ new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
+ mStatsRule.getHandler());
+ powerStatsStore.reset();
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+ mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+ mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+
+ batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
+ /* accumulateBatteryUsageStats */ true);
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ }
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ }
+
+ synchronized (batteryStats) {
+ batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ }
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+ }
+ setTime(55 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ // This section has not been saved yet, but should be added to the accumulated totals
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ }
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+ }
+ setTime(115 * MINUTE_IN_MS);
+
+ // Await completion
+ ConditionVariable done = new ConditionVariable();
+ mStatsRule.getHandler().post(done::open);
+ done.block();
+
+ BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
+ new BatteryUsageStatsQuery.Builder().accumulated().build());
+
+ assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+ assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
+
+ // Section 1 (saved): 20 - 10 = 10
+ // Section 2 (saved): 50 - 30 = 20
+ // Section 3 (fresh): 110 - 80 = 30
+ // Total: 10 + 20 + 30 = 60
+ assertThat(stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(0.0001)
+ .of(360.0); // 360 mA * 1.0 hour
+ assertThat(stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60 * MINUTE_IN_MS);
+
+ final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
+ .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
+ assertThat(uidBatteryConsumer
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(0.1)
+ .of(360.0);
+ assertThat(uidBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60 * MINUTE_IN_MS);
+ }
+
private void setTime(long timeMs) {
mMockClock.currentTime = timeMs;
mMockClock.realtime = timeMs;
@@ -550,7 +647,8 @@
return null;
}).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
- mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+ mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore,
+ /* accumulateBatteryUsageStats */ false);
// Make an incompatible change of supported energy components. This will trigger
// a BatteryStats reset, which will generate a snapshot of battery stats.
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index bbe0755..ac1b7c6 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -54,6 +54,7 @@
"services.flags",
"services.net",
"services.people",
+ "services.supervision",
"services.usage",
"service-permission.stubs.system_server",
"guava",
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
new file mode 100644
index 0000000..6bd4279
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 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.supervision
+
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.server.LocalServices
+import com.android.server.pm.UserManagerInternal
+import com.google.common.truth.Truth.assertThat
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+/**
+ * Unit tests for {@link SupervisionService}.
+ * <p/>
+ * Run with <code>atest SupervisionServiceTest</code>.
+ */
+@RunWith(AndroidJUnit4::class)
+class SupervisionServiceTest {
+ companion object {
+ const val USER_ID = 100
+ }
+
+ private lateinit var service: SupervisionService
+
+ @Rule
+ @JvmField
+ val mocks: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var mockUserManagerInternal: UserManagerInternal
+
+ @Before
+ fun setup() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+
+ LocalServices.removeServiceForTest(UserManagerInternal::class.java)
+ LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
+
+ service = SupervisionService(context)
+ }
+
+ @Test
+ fun testSetSupervisionEnabledForUser() {
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+
+ service.setSupervisionEnabledForUser(USER_ID, true)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+
+ service.setSupervisionEnabledForUser(USER_ID, false)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun testSetSupervisionLockscreenEnabledForUser() {
+ var userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isFalse()
+ assertThat(userData.supervisionLockScreenOptions).isNull()
+
+ service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, true, Bundle())
+ userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isTrue()
+ assertThat(userData.supervisionLockScreenOptions).isNotNull()
+
+ service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, false, null)
+ userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isFalse()
+ assertThat(userData.supervisionLockScreenOptions).isNull()
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b5258df..60fa52f 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -402,4 +402,73 @@
// Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
verify(mWindowManager, times(0)).updateViewLayout(any(), any());
}
-}
\ No newline at end of file
+
+ @Test
+ public void testPinchDrag() {
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent pointerDown = new MotionEventBuilder(MotionEvent.ACTION_POINTER_DOWN,
+ SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(pointerDown);
+
+ // Simulate ACTION_MOVE event (both fingers moving apart).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .rawXCursorPosition(mWindowLayoutParams.x + 10f)
+ .rawYCursorPosition(mWindowLayoutParams.y + 10f)
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ MotionEvent pointerUp = new MotionEventBuilder(MotionEvent.ACTION_POINTER_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(pointerUp);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+}