Merge changes I84f108ba,Ia313f5f3,Id8c29545,I405ad66f into main
* changes:
STL onStop remove dragController if needed
Revert^2 "PriorityNestedScrollConnection: make onStop suspendable and add onCancel"
STL nested scroll keeps priority even if it cannot scroll
Revert^2 "Simplify PriorityNestedScrollConnection"
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index deb6f13..1ae9ada 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -789,7 +789,6 @@
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
- "com.android.healthfitness",
"com.android.permission",
"com.android.nfcservices",
],
diff --git a/apct-tests/perftests/tracing/Android.bp b/apct-tests/perftests/tracing/Android.bp
index 08e365b..8814216 100644
--- a/apct-tests/perftests/tracing/Android.bp
+++ b/apct-tests/perftests/tracing/Android.bp
@@ -22,6 +22,7 @@
"apct-perftests-utils",
"collector-device-lib",
"platform-test-annotations",
+ "perfetto_trace_java_protos",
],
test_suites: [
"device-tests",
diff --git a/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
index f4759b8..7ef8c53 100644
--- a/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
+++ b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
@@ -17,10 +17,12 @@
import android.app.Activity;
import android.os.Bundle;
+import android.os.ServiceManager.ServiceNotFoundException;
import android.perftests.utils.Stats;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
@@ -31,6 +33,8 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+import perfetto.protos.ProtologCommon;
+
import java.util.ArrayList;
import java.util.Collection;
@@ -65,24 +69,48 @@
};
}
+ private IProtoLog mProcessedProtoLogger;
+ private static final String MOCK_TEST_FILE_PATH = "mock/file/path";
+ private static final perfetto.protos.Protolog.ProtoLogViewerConfig VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TestProtoLogGroup.TEST_GROUP.toString())
+ .setTag(TestProtoLogGroup.TEST_GROUP.getTag())
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(123)
+ .setMessage("My Test Debug Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:123")
+ ).build();
+
@BeforeClass
public static void init() {
ProtoLog.init(TestProtoLogGroup.values());
}
@Before
- public void setUp() {
+ public void setUp() throws ServiceNotFoundException {
TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);
+
+ mProcessedProtoLogger = new ProcessedPerfettoProtoLogImpl(
+ MOCK_TEST_FILE_PATH,
+ () -> new AutoClosableProtoInputStream(VIEWER_CONFIG.toByteArray()),
+ () -> {},
+ TestProtoLogGroup.values()
+ );
}
@Test
public void log_Processed_NoArgs() {
- final var protoLog = ProtoLog.getSingleInstance();
final var perfMonitor = new PerfMonitor();
while (perfMonitor.keepRunning()) {
- protoLog.log(
+ mProcessedProtoLogger.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
0, (Object[]) null);
}
@@ -90,11 +118,10 @@
@Test
public void log_Processed_WithArgs() {
- final var protoLog = ProtoLog.getSingleInstance();
final var perfMonitor = new PerfMonitor();
while (perfMonitor.keepRunning()) {
- protoLog.log(
+ mProcessedProtoLogger.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
0b1110101001010100,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
index 676cbc7..f820883 100644
--- a/apex/blobstore/OWNERS
+++ b/apex/blobstore/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 25692
+# Bug component: 1628187
set noparent
sudheersai@google.com
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 98e53ab..810be8f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -88,4 +88,11 @@
namespace: "backstage_power"
description: "Adjust quota default parameters"
bug: "347058927"
+}
+
+flag {
+ name: "enforce_quota_policy_to_top_started_jobs"
+ namespace: "backstage_power"
+ description: "Apply the quota policy to jobs started when the app was in TOP state"
+ bug: "374323858"
}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 885bad5..37e2fe2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -619,7 +619,7 @@
}
final int uid = jobStatus.getSourceUid();
- if (mTopAppCache.get(uid)) {
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) {
if (DEBUG) {
Slog.d(TAG, jobStatus.toShortString() + " is top started job");
}
@@ -656,7 +656,9 @@
timer.stopTrackingJob(jobStatus);
}
}
- mTopStartedJobs.remove(jobStatus);
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs()) {
+ mTopStartedJobs.remove(jobStatus);
+ }
}
@Override
@@ -767,7 +769,7 @@
/** @return true if the job was started while the app was in the TOP state. */
private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
- return mTopStartedJobs.contains(jobStatus);
+ return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus);
}
/** Returns the maximum amount of time this job could run for. */
@@ -4379,11 +4381,13 @@
@Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
- pw.println("Flags: ");
+ pw.println("Aconfig Flags:");
pw.println(" " + Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS
+ ": " + Flags.adjustQuotaDefaultConstants());
pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS
+ ": " + Flags.enforceQuotaPolicyToFgsJobs());
+ pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS
+ + ": " + Flags.enforceQuotaPolicyToTopStartedJobs());
pw.println();
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
diff --git a/core/api/current.txt b/core/api/current.txt
index 911e7de..a8b9e33 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11240,6 +11240,7 @@
method public void removeCategory(String);
method public void removeExtra(String);
method public void removeFlags(int);
+ method @FlaggedApi("android.security.prevent_intent_redirect") public void removeLaunchSecurityProtection();
method @NonNull public android.content.Intent replaceExtras(@NonNull android.content.Intent);
method @NonNull public android.content.Intent replaceExtras(@Nullable android.os.Bundle);
method public android.content.ComponentName resolveActivity(@NonNull android.content.pm.PackageManager);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1ff8c51..79bea01 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1892,8 +1892,7 @@
}
@FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
- ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
- ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+ ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
}
public static class SoundTrigger.RecognitionEvent {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e7f4dbc..b0a8b1b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,6 +237,7 @@
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
+import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.policy.DecorView;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
@@ -2680,7 +2681,10 @@
handleUnstableProviderDied((IBinder)msg.obj, false);
break;
case REQUEST_ASSIST_CONTEXT_EXTRAS:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "handleRequestAssistContextExtras");
handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case TRANSLUCENT_CONVERSION_COMPLETE:
handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
@@ -7675,6 +7679,16 @@
}
}
});
+
+ // Register callback to report native memory metrics post GC cleanup
+ if (Flags.reportPostgcMemoryMetrics() &&
+ com.android.libcore.readonly.Flags.postCleanupApis()) {
+ VMRuntime.addPostCleanupCallback(new Runnable() {
+ @Override public void run() {
+ MetricsLoggerWrapper.logPostGcMemorySnapshot();
+ }
+ });
+ }
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ed6b851..fb5a12b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -132,6 +132,7 @@
import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.util.UserIcons;
import dalvik.system.VMRuntime;
@@ -822,6 +823,16 @@
@Override
public Boolean recompute(HasSystemFeatureQuery query) {
try {
+ // As an optimization, check first to see if the feature was defined at
+ // compile-time as either available or unavailable.
+ // TODO(b/203143243): Consider hoisting this optimization out of the cache
+ // after the trunk stable (build) flag has soaked and more features are
+ // defined at compile-time.
+ Boolean maybeHasSystemFeature =
+ RoSystemFeatures.maybeHasFeature(query.name, query.version);
+ if (maybeHasSystemFeature != null) {
+ return maybeHasSystemFeature.booleanValue();
+ }
return ActivityThread.currentActivityThread().getPackageManager().
hasSystemFeature(query.name, query.version);
} catch (RemoteException e) {
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e4d3baa..acedef0 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -21,11 +21,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -78,10 +80,15 @@
public abstract @Nullable R apply(@NonNull Q query);
/**
- * Return true if a query should not use the cache. The default implementation
- * always uses the cache.
+ * Return true if a query should not use the cache. The default implementation returns true
+ * if the process UID differs from the calling UID. This is to prevent a binder caller from
+ * reading a cached value created due to a different binder caller, when processes are
+ * caching on behalf of other processes.
*/
public boolean shouldBypassCache(@NonNull Q query) {
+ if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) {
+ return Binder.getCallingUid() != Process.myUid();
+ }
return false;
}
};
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index e882bb5..081ce31 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -345,6 +345,15 @@
*/
public AppCompatTaskInfo appCompatTaskInfo = AppCompatTaskInfo.create();
+ /**
+ * The top activity's main window frame if it doesn't match the top activity bounds.
+ * {@code null}, otherwise.
+ *
+ * @hide
+ */
+ @Nullable
+ public Rect topActivityMainWindowFrame;
+
TaskInfo() {
// Do nothing
}
@@ -477,7 +486,8 @@
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& requestedVisibleTypes == that.requestedVisibleTypes
- && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
+ && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
+ && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
}
/**
@@ -553,6 +563,7 @@
capturedLinkTimestamp = source.readLong();
requestedVisibleTypes = source.readInt();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
+ topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
}
/**
@@ -606,6 +617,7 @@
dest.writeLong(capturedLinkTimestamp);
dest.writeInt(requestedVisibleTypes);
dest.writeTypedObject(appCompatTaskInfo, flags);
+ dest.writeTypedObject(topActivityMainWindowFrame, flags);
}
@Override
@@ -649,6 +661,7 @@
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " requestedVisibleTypes=" + requestedVisibleTypes
+ " appCompatTaskInfo=" + appCompatTaskInfo
+ + " topActivityMainWindowFrame=" + topActivityMainWindowFrame
+ "}";
}
}
diff --git a/core/java/android/app/metrics.aconfig b/core/java/android/app/metrics.aconfig
new file mode 100644
index 0000000..488f1c7
--- /dev/null
+++ b/core/java/android/app/metrics.aconfig
@@ -0,0 +1,10 @@
+package: "android.app"
+container: "system"
+
+flag {
+ namespace: "system_performance"
+ name: "report_postgc_memory_metrics"
+ is_exported: false
+ description: "Controls whether to report memory metrics post GC cleanup"
+ bug: "331243037"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 37fa9a26..1d4c18f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -286,4 +286,11 @@
namespace: "systemui"
description: "Adds logging for notification/modes backup and restore events"
bug: "289524803"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "notification_classification_ui"
+ namespace: "systemui"
+ description: "Adds UI for NAS classification of notifications"
+ bug: "367996732"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f719528..e8cec70 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,6 +20,7 @@
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
+import static android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT;
import static android.security.Flags.preventIntentRedirect;
import android.Manifest;
@@ -40,7 +41,10 @@
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -670,6 +674,11 @@
public class Intent implements Parcelable, Cloneable {
private static final String TAG = "Intent";
+ /** @hide */
+ @ChangeId
+ @Overridable
+ public static final long ENABLE_PREVENT_INTENT_REDIRECT = 29076063L;
+
private static final String ATTR_ACTION = "action";
private static final String TAG_CATEGORIES = "categories";
private static final String ATTR_CATEGORY = "category";
@@ -12240,7 +12249,7 @@
* @hide
*/
public void collectExtraIntentKeys() {
- if (!preventIntentRedirect()) return;
+ if (!isPreventIntentRedirectEnabled()) return;
if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
@@ -12257,6 +12266,14 @@
}
}
+ /**
+ * @hide
+ */
+ public static boolean isPreventIntentRedirectEnabled() {
+ return preventIntentRedirect() && CompatChanges.isChangeEnabled(
+ ENABLE_PREVENT_INTENT_REDIRECT);
+ }
+
/** @hide */
public void checkCreatorToken() {
if (mExtras == null) return;
@@ -12281,6 +12298,20 @@
mExtras.setIsIntentExtra();
}
+ /**
+ * When an intent comes from another app or component as an embedded extra intent, the system
+ * creates a token to identify the creator of this foreign intent. If this token is missing or
+ * invalid, the system will block the launch of this intent. If it contains a valid token, the
+ * system will perform verification against the creator to block launching target it has no
+ * permission to launch or block it from granting URI access to the tagert it cannot access.
+ * This method provides a way to opt out this feature.
+ */
+ @FlaggedApi(FLAG_PREVENT_INTENT_REDIRECT)
+ public void removeLaunchSecurityProtection() {
+ mExtendedFlags &= ~EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN;
+ removeCreatorTokenInfo();
+ }
+
public void writeToParcel(Parcel out, int flags) {
out.writeString8(mAction);
Uri.writeToParcel(out, mData);
@@ -12331,7 +12362,7 @@
out.writeInt(0);
}
- if (preventIntentRedirect()) {
+ if (isPreventIntentRedirectEnabled()) {
if (mCreatorTokenInfo == null) {
out.writeInt(0);
} else {
@@ -12398,7 +12429,7 @@
mOriginalIntent = new Intent(in);
}
- if (preventIntentRedirect()) {
+ if (isPreventIntentRedirectEnabled()) {
if (in.readInt() != 0) {
mCreatorTokenInfo = new CreatorTokenInfo();
mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5b38942..5f439b1 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -342,3 +342,11 @@
bug: "292261144"
is_fixed_read_only: true
}
+
+flag {
+ name: "change_launcher_badging"
+ namespace: "package_manager_service"
+ description: "Feature flag to introduce a new way to change the launcher badging."
+ bug: "364760703"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 047d1fa..26ffa11 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -39,3 +39,11 @@
description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
bug: "322081563"
}
+
+flag {
+ name: "screen_off_unlock_udfps"
+ is_exported: true
+ namespace: "biometrics_integration"
+ description: "This flag controls Whether to enable fp unlock when screen turns off on udfps devices"
+ bug: "373792870"
+}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 47541ca..59a602ca 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -18,6 +18,7 @@
import android.annotation.TestApi;
import android.content.Context;
+import android.hardware.biometrics.Flags;
import android.os.Build;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -41,6 +42,7 @@
private final Context mContext;
private final boolean mAlwaysOnByDefault;
private final boolean mPickupGestureEnabledByDefault;
+ private final boolean mScreenOffUdfpsEnabledByDefault;
/** Copied from android.provider.Settings.Secure since these keys are hidden. */
private static final String[] DOZE_SETTINGS = {
@@ -68,6 +70,8 @@
mAlwaysOnByDefault = mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnEnabled);
mPickupGestureEnabledByDefault =
mContext.getResources().getBoolean(R.bool.config_dozePickupGestureEnabled);
+ mScreenOffUdfpsEnabledByDefault =
+ mContext.getResources().getBoolean(R.bool.config_screen_off_udfps_enabled);
}
/** @hide */
@@ -146,7 +150,9 @@
/** @hide */
public boolean screenOffUdfpsEnabled(int user) {
return !TextUtils.isEmpty(udfpsLongPressSensorType())
- && boolSettingDefaultOff("screen_off_udfps_enabled", user);
+ && ((mScreenOffUdfpsEnabledByDefault && Flags.screenOffUnlockUdfps())
+ ? boolSettingDefaultOn("screen_off_udfps_enabled", user)
+ : boolSettingDefaultOff("screen_off_udfps_enabled", user));
}
/** @hide */
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 5ee61bc..2df5418 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -99,6 +99,7 @@
public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59;
public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
+ public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62;
public static final int FLAG_CANCELLED = 1;
@@ -175,7 +176,7 @@
KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
-
+ KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -415,6 +416,8 @@
case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+ case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MOVE_TO_NEXT_DISPLAY;
case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
case KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -530,6 +533,8 @@
return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT";
case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT";
+ case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
+ return "KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY";
case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
case KEY_GESTURE_TYPE_LOCK_SCREEN:
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 22ae676..2ba1078 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -40,6 +40,7 @@
import android.system.ErrnoException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;
@@ -170,17 +171,18 @@
public static SoundTrigger.RecognitionConfig aidl2apiRecognitionConfig(
RecognitionConfig aidlConfig) {
- var keyphrases =
- new SoundTrigger.KeyphraseRecognitionExtra[aidlConfig.phraseRecognitionExtras.length];
- int i = 0;
+ var keyphrases = new ArrayList<SoundTrigger.KeyphraseRecognitionExtra>(
+ aidlConfig.phraseRecognitionExtras.length);
for (var extras : aidlConfig.phraseRecognitionExtras) {
- keyphrases[i++] = aidl2apiPhraseRecognitionExtra(extras);
+ keyphrases.add(aidl2apiPhraseRecognitionExtra(extras));
}
- return new SoundTrigger.RecognitionConfig(aidlConfig.captureRequested,
- false /** allowMultipleTriggers **/,
- keyphrases,
- Arrays.copyOf(aidlConfig.data, aidlConfig.data.length),
- aidl2apiAudioCapabilities(aidlConfig.audioCapabilities));
+ return new SoundTrigger.RecognitionConfig.Builder()
+ .setCaptureRequested(aidlConfig.captureRequested)
+ .setAllowMultipleTriggers(false)
+ .setKeyphrases(keyphrases)
+ .setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
+ .setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
+ .build();
}
public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 05e91e4..a1e7567 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1529,8 +1529,6 @@
* config that can be used by
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
*
- * @deprecated should use builder-based constructor instead.
- * TODO(b/368042125): remove this method.
* @param captureRequested Whether the DSP should capture the trigger sound.
* @param allowMultipleTriggers Whether the service should restart listening after the DSP
* triggers.
@@ -1538,15 +1536,10 @@
* @param data Opaque data for use by system applications who know about voice engine
* internals, typically during enrollment.
* @param audioCapabilities Bit field encoding of the AudioCapabilities.
- *
- * @hide
*/
- @Deprecated
- @SuppressWarnings("Todo")
- @TestApi
- public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
- @Nullable byte[] data, int audioCapabilities) {
+ private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+ int audioCapabilities) {
this.mCaptureRequested = captureRequested;
this.mAllowMultipleTriggers = allowMultipleTriggers;
this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1558,6 +1551,7 @@
* Constructor for {@link RecognitionConfig} without audioCapabilities. The
* audioCapabilities is set to 0.
*
+ * @deprecated Use {@link Builder} instead.
* @param captureRequested Whether the DSP should capture the trigger sound.
* @param allowMultipleTriggers Whether the service should restart listening after the DSP
* triggers.
@@ -1567,10 +1561,10 @@
* @hide
*/
@UnsupportedAppUsage
+ @Deprecated
@TestApi
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
- @Nullable byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
@@ -1718,7 +1712,7 @@
/**
* Sets capture requested state.
- * @param captureRequested The new requested state.
+ * @param captureRequested Whether the DSP should capture the trigger sound.
* @return the same Builder instance.
*/
public @NonNull Builder setCaptureRequested(boolean captureRequested) {
@@ -1728,7 +1722,8 @@
/**
* Sets allow multiple triggers state.
- * @param allowMultipleTriggers The new allow multiple triggers state.
+ * @param allowMultipleTriggers Whether the service should restart listening after the
+ * DSP triggers.
* @return the same Builder instance.
*/
public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
@@ -1738,7 +1733,8 @@
/**
* Sets the keyphrases field.
- * @param keyphrases The new keyphrases.
+ * @param keyphrases The list of keyphrase specific data associated with this
+ * recognition session.
* @return the same Builder instance.
*/
public @NonNull Builder setKeyphrases(
@@ -1749,7 +1745,9 @@
/**
* Sets the data field.
- * @param data The new data.
+ * @param data Opaque data provided to the DSP associated with this recognition session,
+ * which is used by system applications who know about voice engine
+ * internals, typically during enrollment.
* @return the same Builder instance.
*/
public @NonNull Builder setData(@Nullable byte[] data) {
@@ -1759,7 +1757,8 @@
/**
* Sets the audio capabilities field.
- * @param audioCapabilities The new audio capabilities.
+ * @param audioCapabilities The bit field encoding of the audio capabilities associated
+ * with this recognition session.
* @return the same Builder instance.
*/
public @NonNull Builder setAudioCapabilities(int audioCapabilities) {
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 46cf016..c0398ce 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -40,9 +40,9 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.vcn.util.PersistableBundleUtils;
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1c9be6f..f275714 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -28,13 +28,13 @@
import android.content.pm.PackageManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
-import android.os.Binder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.net.module.util.BinderUtils;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -711,7 +711,7 @@
@Override
public void onPolicyChanged() {
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> mExecutor.execute(() -> mListener.onPolicyChanged()));
}
}
@@ -734,7 +734,7 @@
@Override
public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
}
@@ -747,7 +747,7 @@
@Nullable String exceptionMessage) {
final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() ->
mExecutor.execute(
() ->
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index edf2c09..16114dd 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -21,10 +21,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.PersistableBundle;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 2e6b09f..c7b2f18 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -29,9 +29,9 @@
import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.ArrayList;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index ddcb5c6..6c3c285 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -24,6 +24,8 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.MonotonicClock;
@@ -43,7 +45,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
@@ -126,6 +130,12 @@
// Max window size. CursorWindow uses only as much memory as needed.
private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 20_000_000; // bytes
+ /**
+ * Used by tests to ensure all BatteryUsageStats instances are closed.
+ */
+ @VisibleForTesting
+ public static boolean DEBUG_INSTANCE_COUNT;
+
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
@@ -153,7 +163,7 @@
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final BatteryStatsHistory mBatteryStatsHistory;
- private BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
+ private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private CursorWindow mBatteryConsumersCursorWindow;
private BatteryUsageStats(@NonNull Builder builder) {
@@ -873,6 +883,7 @@
@Override
public void close() throws IOException {
+ onCursorWindowReleased(mBatteryConsumersCursorWindow);
mBatteryConsumersCursorWindow.close();
mBatteryConsumersCursorWindow = null;
}
@@ -880,6 +891,7 @@
@Override
protected void finalize() throws Throwable {
if (mBatteryConsumersCursorWindow != null) {
+ // Do not decrement sOpenCusorWindowCount. All instances should be closed explicitly
mBatteryConsumersCursorWindow.close();
}
super.finalize();
@@ -934,6 +946,7 @@
boolean includesPowerStateData, double minConsumedPowerThreshold) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
+ onCursorWindowAllocated(mBatteryConsumersCursorWindow);
mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
customPowerComponentNames, includePowerModels, includeProcessStateData,
includeScreenStateData, includesPowerStateData);
@@ -996,6 +1009,7 @@
*/
public void discard() {
mBatteryConsumersCursorWindow.close();
+ onCursorWindowReleased(mBatteryConsumersCursorWindow);
}
/**
@@ -1264,4 +1278,50 @@
}
}
}
+
+ @GuardedBy("BatteryUsageStats.class")
+ private static Map<CursorWindow, Exception> sInstances;
+
+ private static void onCursorWindowAllocated(CursorWindow window) {
+ if (!DEBUG_INSTANCE_COUNT) {
+ return;
+ }
+
+ synchronized (BatteryUsageStats.class) {
+ if (sInstances == null) {
+ sInstances = new HashMap<>();
+ }
+ sInstances.put(window, new Exception());
+ }
+ }
+
+ private static void onCursorWindowReleased(CursorWindow window) {
+ if (!DEBUG_INSTANCE_COUNT) {
+ return;
+ }
+
+ synchronized (BatteryUsageStats.class) {
+ sInstances.remove(window);
+ }
+ }
+
+ /**
+ * Used by tests to ensure all BatteryUsageStats instances are closed.
+ */
+ @VisibleForTesting
+ public static void assertAllInstancesClosed() {
+ if (!DEBUG_INSTANCE_COUNT) {
+ throw new IllegalStateException("DEBUG_INSTANCE_COUNT is false");
+ }
+
+ synchronized (BatteryUsageStats.class) {
+ if (!sInstances.isEmpty()) {
+ Exception callSite = sInstances.entrySet().iterator().next().getValue();
+ int count = sInstances.size();
+ sInstances.clear();
+ throw new IllegalStateException(
+ "Instances of BatteryUsageStats not closed: " + count, callSite);
+ }
+ }
+ }
}
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 9419032..9dec867 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -316,9 +316,7 @@
* @return True if the hardware can control the frequency of the vibrations, otherwise false.
*/
public boolean hasFrequencyControl() {
- // We currently can only control frequency of the vibration using the compose PWLE method.
- return hasCapability(
- IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ return hasCapability(IVibrator.CAP_FREQUENCY_CONTROL);
}
/**
@@ -481,7 +479,8 @@
* @return True if the hardware supports creating envelope effects, false otherwise.
*/
public boolean areEnvelopeEffectsSupported() {
- return hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ return hasCapability(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
}
/**
diff --git a/core/java/android/security/forensic/ForensicEvent.aidl b/core/java/android/security/forensic/ForensicEvent.aidl
new file mode 100644
index 0000000..a321fb0
--- /dev/null
+++ b/core/java/android/security/forensic/ForensicEvent.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.security.forensic;
+
+/** {@hide} */
+parcelable ForensicEvent;
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
new file mode 100644
index 0000000..9cbc5ec
--- /dev/null
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.security.forensic;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Flags;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * A class that represents a forensic event.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_AFL_API)
+public final class ForensicEvent implements Parcelable {
+ private static final String TAG = "ForensicEvent";
+
+ @NonNull
+ private final String mType;
+
+ @NonNull
+ private final Map<String, String> mKeyValuePairs;
+
+ public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ public ForensicEvent createFromParcel(Parcel in) {
+ return new ForensicEvent(in);
+ }
+
+ public ForensicEvent[] newArray(int size) {
+ return new ForensicEvent[size];
+ }
+ };
+
+ public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) {
+ mType = type;
+ mKeyValuePairs = keyValuePairs;
+ }
+
+ private ForensicEvent(@NonNull Parcel in) {
+ mType = in.readString();
+ mKeyValuePairs = new ArrayMap<>(in.readInt());
+ in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mType);
+ out.writeInt(mKeyValuePairs.size());
+ out.writeMap(mKeyValuePairs);
+ }
+
+ @FlaggedApi(Flags.FLAG_AFL_API)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ForensicEvent{"
+ + "mType=" + mType
+ + ", mKeyValuePairs=" + mKeyValuePairs
+ + '}';
+ }
+}
diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IBackupTransport.aidl
new file mode 100644
index 0000000..c2cbc83
--- /dev/null
+++ b/core/java/android/security/forensic/IBackupTransport.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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 android.security.forensic;
+import android.security.forensic.ForensicEvent;
+
+import com.android.internal.infra.AndroidFuture;
+
+/** {@hide} */
+oneway interface IBackupTransport {
+ /**
+ * Initialize the server side.
+ */
+ void initialize(in AndroidFuture<int> resultFuture);
+
+ /**
+ * Send forensic logging data to the backup destination.
+ * The data is a list of ForensicEvent.
+ * The ForensicEvent is an abstract class that represents
+ * different type of events.
+ */
+ void addData(in List<ForensicEvent> events, in AndroidFuture<int> resultFuture);
+
+ /**
+ * Release the binder to the server.
+ */
+ void release(in AndroidFuture<int> resultFuture);
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index b593902a9..9bb1039 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -77,4 +77,11 @@
description: "Prevent intent redirect attacks"
bug: "361143368"
is_fixed_read_only: true
+}
+
+flag {
+ name: "prevent_intent_redirect_abort_or_throw_exception"
+ namespace: "responsible_apis"
+ description: "Prevent intent redirect attacks by aborting or throwing security exception"
+ bug: "361143368"
}
\ No newline at end of file
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index ccc17ec..2e660fc 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -73,6 +73,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -1513,10 +1514,11 @@
"Recognition for the given keyphrase is not supported");
}
- KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
+ List<KeyphraseRecognitionExtra> recognitionExtra =
+ new ArrayList<KeyphraseRecognitionExtra>(1);
// TODO: Do we need to do something about the confidence level here?
- recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
- mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]);
+ recognitionExtra.add(new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
+ mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]));
boolean captureTriggerAudio =
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =
@@ -1534,10 +1536,17 @@
int code;
try {
code = mSoundTriggerSession.startRecognition(
- mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback,
- new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- recognitionExtra, data, audioCapabilities),
- runInBatterySaver);
+ mKeyphraseMetadata.getId(),
+ mLocale.toLanguageTag(),
+ mInternalCallback,
+ new RecognitionConfig.Builder()
+ .setCaptureRequested(captureTriggerAudio)
+ .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setKeyphrases(recognitionExtra)
+ .setData(data)
+ .setAudioCapabilities(audioCapabilities)
+ .build(),
+ runInBatterySaver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e8ef9d6..bce51f2 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1701,6 +1701,11 @@
public final void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
// not supported on the deprecated interface - Use TelephonyCallback instead
}
+
+ public final void onCarrierRoamingNtnAvailableServicesChanged(
+ @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 5295b60..46e27dc 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -681,6 +681,20 @@
public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
/**
+ * Event for listening to changes in carrier roaming non-terrestrial network available services
+ * via callback onCarrierRoamingNtnAvailableServicesChanged().
+ * This callback is triggered when the available services provided by the carrier roaming
+ * satellite changes. The carrier roaming satellite is defined by the following conditions.
+ * <ul>
+ * <li>Subscription supports attaching to satellite which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -726,7 +740,8 @@
EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
- EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED
+ EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED,
+ EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1784,6 +1799,15 @@
* </ul>
*/
default void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {}
+
+ /**
+ * Callback invoked when carrier roaming non-terrestrial network available
+ * service changes.
+ *
+ * @param availableServices The list of the supported services.
+ */
+ default void onCarrierRoamingNtnAvailableServicesChanged(
+ @NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
}
/**
@@ -2235,5 +2259,19 @@
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> listener.onCarrierRoamingNtnEligibleStateChanged(eligible)));
}
+
+ public void onCarrierRoamingNtnAvailableServicesChanged(
+ @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+ if (!Flags.carrierRoamingNbIotNtn()) return;
+
+ CarrierRoamingNtnModeListener listener =
+ (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ List<Integer> ServiceList = Arrays.stream(availableServices).boxed()
+ .collect(Collectors.toList());
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 3c7e924..4d50a45 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -1118,6 +1118,21 @@
}
/**
+ * Notify external listeners that carrier roaming non-terrestrial available services changed.
+ * @param availableServices The list of the supported services.
+ * @hide
+ */
+ public void notifyCarrierRoamingNtnAvailableServicesChanged(
+ int subId, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+ try {
+ sRegistry.notifyCarrierRoamingNtnAvailableServicesChanged(subId, availableServices);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Processes potential event changes from the provided {@link TelephonyCallback}.
*
* @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1272,12 +1287,9 @@
if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
- }
-
- if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
+ eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED);
}
-
return eventList;
}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 6448f10..003393c 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -230,7 +230,7 @@
mRendererWrapper = mSurfaceOnly ? null : renderer;
mMetricsWrapper = mSurfaceOnly ? null : metrics;
mViewRoot = mSurfaceOnly ? null : viewRootWrapper;
- mObserver = mSurfaceOnly
+ mObserver = mSurfaceOnly || (Flags.useSfFrameDuration() && Flags.ignoreHwuiIsFirstFrame())
? null
: new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
mHandler, /* waitForPresentTime= */ false);
@@ -253,6 +253,7 @@
mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
+ Trace.beginSection("FrameTracker#surfaceCreated");
mHandler.runWithScissors(() -> {
if (mSurfaceControl == null) {
mSurfaceControl = mViewRoot.getSurfaceControl();
@@ -262,6 +263,7 @@
}
}
}, EXECUTOR_TASK_TIMEOUT);
+ Trace.endSection();
}
@Override
@@ -464,23 +466,28 @@
@Override
public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
postCallback(() -> {
- if (mCancelled || mMetricsFinalized) {
- return;
- }
+ try {
+ Trace.beginSection("FrameTracker#onJankDataAvailable");
+ if (mCancelled || mMetricsFinalized) {
+ return;
+ }
- for (SurfaceControl.JankData jankStat : jankData) {
- if (!isInRange(jankStat.frameVsyncId)) {
- continue;
+ for (SurfaceControl.JankData jankStat : jankData) {
+ if (!isInRange(jankStat.frameVsyncId)) {
+ continue;
+ }
+ JankInfo info = findJankInfo(jankStat.frameVsyncId);
+ if (info != null) {
+ info.update(jankStat);
+ } else {
+ mJankInfos.put((int) jankStat.frameVsyncId,
+ JankInfo.createFromSurfaceControlCallback(jankStat));
+ }
}
- JankInfo info = findJankInfo(jankStat.frameVsyncId);
- if (info != null) {
- info.update(jankStat);
- } else {
- mJankInfos.put((int) jankStat.frameVsyncId,
- JankInfo.createFromSurfaceControlCallback(jankStat));
- }
+ processJankInfos();
+ } finally {
+ Trace.endSection();
}
- processJankInfos();
});
}
@@ -507,29 +514,35 @@
@Override
public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
postCallback(() -> {
- if (mCancelled || mMetricsFinalized) {
- return;
- }
+ try {
+ Trace.beginSection("FrameTracker#onFrameMetricsAvailable");
+ if (mCancelled || mMetricsFinalized) {
+ return;
+ }
- // Since this callback might come a little bit late after the end() call.
- // We should keep tracking the begin / end timestamp that we can compare with
- // vsync timestamp to check if the frame is in the duration of the CUJ.
- long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
- boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
- long frameVsyncId =
- mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
+ // Since this callback might come a little bit late after the end() call.
+ // We should keep tracking the begin / end timestamp that we can compare with
+ // vsync timestamp to check if the frame is in the duration of the CUJ.
+ long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
+ boolean isFirstFrame =
+ mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
+ long frameVsyncId =
+ mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
- if (!isInRange(frameVsyncId)) {
- return;
+ if (!isInRange(frameVsyncId)) {
+ return;
+ }
+ JankInfo info = findJankInfo(frameVsyncId);
+ if (info != null) {
+ info.update(totalDurationNanos, isFirstFrame);
+ } else {
+ mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
+ frameVsyncId, totalDurationNanos, isFirstFrame));
+ }
+ processJankInfos();
+ } finally {
+ Trace.endSection();
}
- JankInfo info = findJankInfo(frameVsyncId);
- if (info != null) {
- info.update(totalDurationNanos, isFirstFrame);
- } else {
- mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
- frameVsyncId, totalDurationNanos, isFirstFrame));
- }
- processJankInfos();
});
}
@@ -568,13 +581,20 @@
}
private boolean callbacksReceived(JankInfo info) {
- return mSurfaceOnly
+ return mObserver == null
? info.surfaceControlCallbackFired
: info.hwuiCallbackFired && info.surfaceControlCallbackFired;
}
@UiThread
private void finish() {
+ Trace.beginSection("FrameTracker#finish");
+ finishTraced();
+ Trace.endSection();
+ }
+
+ @UiThread
+ private void finishTraced() {
if (mMetricsFinalized || mCancelled) return;
mMetricsFinalized = true;
@@ -599,7 +619,7 @@
for (int i = 0; i < mJankInfos.size(); i++) {
JankInfo info = mJankInfos.valueAt(i);
final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame;
- if (isFirstDrawn) {
+ if (isFirstDrawn && !Flags.ignoreHwuiIsFirstFrame()) {
continue;
}
if (info.frameVsyncId > mEndVsyncId) {
@@ -636,7 +656,7 @@
}
// TODO (b/174755489): Early latch currently gets fired way too often, so we have
// to ignore it for now.
- if (!mSurfaceOnly && !info.hwuiCallbackFired) {
+ if (mObserver != null && !info.hwuiCallbackFired) {
markEvent("FT#MissedHWUICallback", info.frameVsyncId);
Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
+ ", CUJ=" + name);
@@ -762,7 +782,9 @@
* @param observer observer
*/
public void addObserver(HardwareRendererObserver observer) {
- mRenderer.addObserver(observer);
+ if (observer != null) {
+ mRenderer.addObserver(observer);
+ }
}
/**
@@ -770,7 +792,9 @@
* @param observer observer
*/
public void removeObserver(HardwareRendererObserver observer) {
- mRenderer.removeObserver(observer);
+ if (observer != null) {
+ mRenderer.removeObserver(observer);
+ }
}
}
diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig
index 82f50ae..287ad18 100644
--- a/core/java/com/android/internal/jank/flags.aconfig
+++ b/core/java/com/android/internal/jank/flags.aconfig
@@ -8,3 +8,10 @@
bug: "354763298"
is_fixed_read_only: true
}
+flag {
+ name: "ignore_hwui_is_first_frame"
+ namespace: "window_surfaces"
+ description: "Whether to remove the usage of the HWUI 'is first frame' flag to ignore jank"
+ bug: "354763298"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 07aa720..b56aadd 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1770,6 +1770,10 @@
@GuardedBy("this")
private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ if (cur.eventCode != HistoryItem.EVENT_NONE && cur.eventTag.string == null) {
+ Slog.wtfStack(TAG, "Event " + Integer.toHexString(cur.eventCode) + " without a name");
+ }
+
if (mTracer != null && mTracer.tracingEnabled()) {
recordTraceEvents(cur.eventCode, cur.eventTag);
recordTraceCounters(mTraceLastState, cur.states, STATE1_TRACE_MASK,
@@ -2266,6 +2270,7 @@
private int writeHistoryTag(HistoryTag tag) {
if (tag.string == null) {
Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+ tag.string = "";
}
final int stringLength = tag.string.length();
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index dfb2884..489721f 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -105,7 +105,7 @@
public void setValues(long[] array) {
if (array.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + mLength + ", expected: " + mLength);
+ "Invalid array length: " + array.length + ", expected: " + mLength);
}
native_setValues(mNativeObject, array);
}
@@ -116,7 +116,7 @@
public void getValues(long[] array) {
if (array.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + mLength + ", expected: " + mLength);
+ "Invalid array length: " + array.length + ", expected: " + mLength);
}
native_getValues(mNativeObject, array);
}
@@ -347,6 +347,11 @@
throw new IllegalArgumentException(
"State: " + state + ", outside the range: [0-" + mStateCount + "]");
}
+ if (longArrayContainer.mLength != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + longArrayContainer.mLength
+ + ", expected: " + mLength);
+ }
native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
}
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
similarity index 60%
rename from services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
rename to core/java/com/android/internal/os/ProcfsMemoryUtil.java
index 6cb6dc0..382f6c4 100644
--- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
+++ b/core/java/com/android/internal/os/ProcfsMemoryUtil.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.stats.pull;
+package com.android.internal.os;
-import static android.os.Process.PROC_OUT_STRING;
+import static android.os.Process.*;
import android.annotation.Nullable;
import android.os.Process;
@@ -23,6 +23,7 @@
public final class ProcfsMemoryUtil {
private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
+ private static final int[] OOM_SCORE_ADJ_OUT = new int[] { PROC_NEWLINE_TERM | PROC_OUT_LONG };
private static final String[] STATUS_KEYS = new String[] {
"Uid:",
"VmHWM:",
@@ -38,17 +39,34 @@
private ProcfsMemoryUtil() {}
/**
- * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
- * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available.
+ * Reads memory stats of a process from procfs.
+ *
+ * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+ * /proc/pid/status in kilobytes or null if not available.
*/
@Nullable
public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+ return readMemorySnapshotFromProcfs("/proc/" + pid + "/status");
+ }
+
+ /**
+ * Reads memory stats of the current process from procfs.
+ *
+ * Returns values of the VmHWM, VmRss, AnonRSS, VmSwap, RssShmem fields in
+ * /proc/self/status in kilobytes or null if not available.
+ */
+ @Nullable
+ public static MemorySnapshot readMemorySnapshotFromProcfs() {
+ return readMemorySnapshotFromProcfs("/proc/self/status");
+ }
+
+ private static MemorySnapshot readMemorySnapshotFromProcfs(String path) {
long[] output = new long[STATUS_KEYS.length];
output[0] = -1;
output[3] = -1;
output[4] = -1;
output[5] = -1;
- Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
+ Process.readProcLines(path, STATUS_KEYS, output);
if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) {
// Could not open or parse file.
return null;
@@ -70,14 +88,54 @@
* if the file is not available.
*/
public static String readCmdlineFromProcfs(int pid) {
+ return readCmdlineFromProcfs("/proc/" + pid + "/cmdline");
+ }
+
+ /**
+ * Reads cmdline of the current process from procfs.
+ *
+ * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
+ * if the file is not available.
+ */
+ public static String readCmdlineFromProcfs() {
+ return readCmdlineFromProcfs("/proc/self/cmdline");
+ }
+
+ private static String readCmdlineFromProcfs(String path) {
String[] cmdline = new String[1];
- if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
+ if (!Process.readProcFile(path, CMDLINE_OUT, cmdline, null, null)) {
return "";
}
return cmdline[0];
}
/**
+ * Reads oom_score_adj of a process from procfs
+ *
+ * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+ */
+ public static int readOomScoreAdjFromProcfs(int pid) {
+ return readOomScoreAdjFromProcfs("/proc/" + pid + "/oom_score_adj");
+ }
+
+ /**
+ * Reads oom_score_adj of the current process from procfs
+ *
+ * Returns content of /proc/pid/oom_score_adj. Defaults to 0 if reading fails.
+ */
+ public static int readOomScoreAdjFromProcfs() {
+ return readOomScoreAdjFromProcfs("/proc/self/oom_score_adj");
+ }
+
+ private static int readOomScoreAdjFromProcfs(String path) {
+ long[] oom_score_adj = new long[1];
+ if (Process.readProcFile(path, OOM_SCORE_ADJ_OUT, null, oom_score_adj, null)) {
+ return (int)oom_score_adj[0];
+ }
+ return 0;
+ }
+
+ /**
* Scans all /proc/pid/cmdline entries and returns a mapping between pid and cmdline.
*/
public static SparseArray<String> getProcessCmdlines() {
@@ -109,7 +167,7 @@
/** Reads and parses selected entries of /proc/vmstat. */
@Nullable
- static VmStat readVmStat() {
+ public static VmStat readVmStat() {
long[] vmstat = new long[VMSTAT_KEYS.length];
vmstat[0] = -1;
Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat);
@@ -121,7 +179,7 @@
return result;
}
- static final class VmStat {
+ public static final class VmStat {
public int oomKillCount;
}
}
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index b42ea7d..e2237f6 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -16,9 +16,15 @@
package com.android.internal.os.logging;
+import android.app.Application;
+import android.os.Process;
+import android.util.Log;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.os.ProcfsMemoryUtil;
import com.android.internal.util.FrameworkStatsLog;
+import java.util.Collection;
+import libcore.util.NativeAllocationRegistry;
/**
* Used to wrap different logging calls in one, so that client side code base is clean and more
@@ -49,4 +55,46 @@
}
}
}
+
+ public static void logPostGcMemorySnapshot() {
+ if (!com.android.libcore.Flags.nativeMetrics()) {
+ return;
+ }
+ int pid = Process.myPid();
+ String processName = Application.getProcessName();
+ Collection<NativeAllocationRegistry.Metrics> metrics =
+ NativeAllocationRegistry.getMetrics();
+ int nMetrics = metrics.size();
+
+ String[] classNames = new String[nMetrics];
+ long[] mallocedCount = new long[nMetrics];
+ long[] mallocedBytes = new long[nMetrics];
+ long[] nonmallocedCount = new long[nMetrics];
+ long[] nonmallocedBytes = new long[nMetrics];
+
+ int i = 0;
+ for (NativeAllocationRegistry.Metrics m : metrics) {
+ classNames[i] = m.getClassName();
+ mallocedCount[i] = m.getMallocedCount();
+ mallocedBytes[i] = m.getMallocedBytes();
+ nonmallocedCount[i] = m.getNonmallocedCount();
+ nonmallocedBytes[i] = m.getNonmallocedBytes();
+ i++;
+ }
+
+ ProcfsMemoryUtil.MemorySnapshot m = ProcfsMemoryUtil.readMemorySnapshotFromProcfs();
+ int oom_score_adj = ProcfsMemoryUtil.readOomScoreAdjFromProcfs();
+ FrameworkStatsLog.write(FrameworkStatsLog.POSTGC_MEMORY_SNAPSHOT,
+ m.uid, processName, pid,
+ oom_score_adj,
+ m.rssInKilobytes,
+ m.anonRssInKilobytes,
+ m.swapInKilobytes,
+ m.anonRssInKilobytes + m.swapInKilobytes,
+ classNames,
+ mallocedCount,
+ mallocedBytes,
+ nonmallocedCount,
+ nonmallocedBytes);
+ }
}
diff --git a/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java b/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java
new file mode 100644
index 0000000..1acb34f
--- /dev/null
+++ b/core/java/com/android/internal/protolog/AutoClosableProtoInputStream.java
@@ -0,0 +1,56 @@
+/*
+ * 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.internal.protolog;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoInputStream;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public final class AutoClosableProtoInputStream implements AutoCloseable {
+ @NonNull
+ private final ProtoInputStream mProtoInputStream;
+ @Nullable
+ private final FileInputStream mFileInputStream;
+
+ public AutoClosableProtoInputStream(@NonNull FileInputStream fileInputStream) {
+ mProtoInputStream = new ProtoInputStream(fileInputStream);
+ mFileInputStream = fileInputStream;
+ }
+
+ public AutoClosableProtoInputStream(@NonNull byte[] input) {
+ mProtoInputStream = new ProtoInputStream(input);
+ mFileInputStream = null;
+ }
+
+ /**
+ * @return the ProtoInputStream this class is wrapping
+ */
+ @NonNull
+ public ProtoInputStream get() {
+ return mProtoInputStream;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (mFileInputStream != null) {
+ mFileInputStream.close();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java b/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java
new file mode 100644
index 0000000..15987664
--- /dev/null
+++ b/core/java/com/android/internal/protolog/NoViewerConfigProtoLogImpl.java
@@ -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.internal.protolog;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Class should only be used as a temporary solution to missing viewer config file on device.
+ * In particular this class should only be initialized in Robolectric tests, if it's being used
+ * otherwise please report it.
+ *
+ * @deprecated
+ */
+@Deprecated
+public class NoViewerConfigProtoLogImpl implements IProtoLog {
+ private static final String LOG_TAG = "ProtoLog";
+
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+ Object[] args) {
+ Log.w(LOG_TAG, "ProtoLogging is not available due to missing viewer config file...");
+ logMessage(logLevel, group.getTag(), "PROTOLOG#" + messageHash + "("
+ + Arrays.stream(args).map(Object::toString).collect(Collectors.joining()) + ")");
+ }
+
+ @Override
+ public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
+ logMessage(logLevel, group.getTag(), TextUtils.formatSimple(messageString, args));
+ }
+
+ @Override
+ public boolean isProtoEnabled() {
+ return false;
+ }
+
+ @Override
+ public int startLoggingToLogcat(String[] groups, ILogger logger) {
+ return 0;
+ }
+
+ @Override
+ public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+ return 0;
+ }
+
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return false;
+ }
+
+ @Override
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return List.of();
+ }
+
+ private void logMessage(LogLevel logLevel, String tag, String message) {
+ switch (logLevel) {
+ case VERBOSE -> Log.v(tag, message);
+ case INFO -> Log.i(tag, message);
+ case DEBUG -> Log.d(tag, message);
+ case WARN -> Log.w(tag, message);
+ case ERROR -> Log.e(tag, message);
+ case WTF -> Log.wtf(tag, message);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index a037cb4..a1c987f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -60,18 +60,16 @@
import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
-import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
import com.android.internal.protolog.common.ILogger;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogDataType;
import com.android.internal.protolog.common.LogLevel;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -93,26 +91,18 @@
/**
* A service for the ProtoLog logging system.
*/
-public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
+public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
private static final String LOG_TAG = "ProtoLog";
public static final String NULL_STRING = "null";
private final AtomicInteger mTracingInstances = new AtomicInteger();
@NonNull
- private final ProtoLogDataSource mDataSource;
- @Nullable
- private final ProtoLogViewerConfigReader mViewerConfigReader;
- @Deprecated
- @Nullable
- private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ protected final ProtoLogDataSource mDataSource;
@NonNull
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ protected final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
@NonNull
private final Runnable mCacheUpdater;
- @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
- private final IProtoLogConfigurationService mProtoLogConfigurationService;
-
@NonNull
private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
@NonNull
@@ -121,68 +111,15 @@
private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
private final Lock mBackgroundServiceLock = new ReentrantLock();
- private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ protected ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
- throws ServiceManager.ServiceNotFoundException {
- this(null, null, null, () -> {}, groups);
- }
+ // Set to true once this is ready to accept protolog to logcat requests.
+ private boolean mLogcatReady = false;
- public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
- throws ServiceManager.ServiceNotFoundException {
- this(null, null, null, cacheUpdater, groups);
- }
-
- public PerfettoProtoLogImpl(
- @NonNull String viewerConfigFilePath,
+ protected PerfettoProtoLogImpl(
@NonNull Runnable cacheUpdater,
@NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
- this(viewerConfigFilePath,
- null,
- new ProtoLogViewerConfigReader(() -> {
- try {
- return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
- } catch (FileNotFoundException e) {
- throw new RuntimeException(
- "Failed to load viewer config file " + viewerConfigFilePath, e);
- }
- }),
- cacheUpdater, groups);
- }
-
- @Deprecated
- @VisibleForTesting
- public PerfettoProtoLogImpl(
- @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- @Nullable ProtoLogViewerConfigReader viewerConfigReader,
- @NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups,
- @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
- @NonNull ProtoLogConfigurationService configurationService) {
- this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater,
- groups, dataSourceBuilder, configurationService);
- }
-
- @VisibleForTesting
- public PerfettoProtoLogImpl(
- @Nullable String viewerConfigFilePath,
- @Nullable ProtoLogViewerConfigReader viewerConfigReader,
- @NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups,
- @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
- @NonNull ProtoLogConfigurationService configurationService) {
- this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater,
- groups, dataSourceBuilder, configurationService);
- }
-
- private PerfettoProtoLogImpl(
- @Nullable String viewerConfigFilePath,
- @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- @Nullable ProtoLogViewerConfigReader viewerConfigReader,
- @NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
- this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
- cacheUpdater, groups,
+ this(cacheUpdater, groups,
ProtoLogDataSource::new,
android.tracing.Flags.clientSideProtoLogging() ?
IProtoLogConfigurationService.Stub.asInterface(
@@ -191,19 +128,11 @@
);
}
- private PerfettoProtoLogImpl(
- @Nullable String viewerConfigFilePath,
- @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+ protected PerfettoProtoLogImpl(
@NonNull Runnable cacheUpdater,
@NonNull IProtoLogGroup[] groups,
@NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
@Nullable IProtoLogConfigurationService configurationService) {
- if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) {
- throw new RuntimeException("Only one of viewerConfigFilePath and "
- + "viewerConfigInputStreamProvider can be set");
- }
-
mDataSource = dataSourceBuilder.build(
this::onTracingInstanceStart,
this::onTracingFlush,
@@ -219,55 +148,33 @@
// for some messages logged right after the construction of this class.
mDataSource.register(params);
- if (viewerConfigInputStreamProvider == null && viewerConfigFilePath != null) {
- viewerConfigInputStreamProvider = new ViewerConfigInputStreamProvider() {
- @NonNull
- @Override
- public ProtoInputStream getInputStream() {
- try {
- return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
- } catch (FileNotFoundException e) {
- throw new RuntimeException(
- "Failed to load viewer config file " + viewerConfigFilePath, e);
- }
- }
- };
- }
-
- this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
- this.mViewerConfigReader = viewerConfigReader;
this.mCacheUpdater = cacheUpdater;
registerGroupsLocally(groups);
if (android.tracing.Flags.clientSideProtoLogging()) {
- mProtoLogConfigurationService = configurationService;
- Objects.requireNonNull(mProtoLogConfigurationService,
+ Objects.requireNonNull(configurationService,
"A null ProtoLog Configuration Service was provided!");
try {
- var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
-
- if (viewerConfigFilePath != null) {
- args.setViewerConfigFile(viewerConfigFilePath);
- }
+ var args = createConfigurationServiceRegisterClientArgs();
final var groupArgs = Stream.of(groups)
- .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .map(group -> new RegisterClientArgs
.GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(ProtoLogConfigurationServiceImpl
- .RegisterClientArgs.GroupConfig[]::new);
+ .toArray(RegisterClientArgs.GroupConfig[]::new);
args.setGroups(groupArgs);
- mProtoLogConfigurationService.registerClient(this, args);
+ configurationService.registerClient(this, args);
} catch (RemoteException e) {
throw new RuntimeException("Failed to register ProtoLog client");
}
- } else {
- mProtoLogConfigurationService = null;
}
}
+ @NonNull
+ protected abstract RegisterClientArgs createConfigurationServiceRegisterClientArgs();
+
/**
* Main log method, do not call directly.
*/
@@ -334,9 +241,6 @@
* @return status code
*/
public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
- if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(groups, logger);
- }
return setTextLogging(true, logger, groups);
}
@@ -347,9 +251,6 @@
* @return status code
*/
public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
- if (mViewerConfigReader != null) {
- mViewerConfigReader.unloadViewerConfig(groups, logger);
- }
return setTextLogging(false, logger, groups);
}
@@ -372,21 +273,8 @@
// we might want to manually specify an id for the group with a collision
verifyNoCollisionsOrDuplicates(protoLogGroups);
- final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
mLogGroups.put(protoLogGroup.name(), protoLogGroup);
-
- if (protoLogGroup.isLogToLogcat()) {
- groupsLoggingToLogcat.add(protoLogGroup.name());
- }
- }
-
- if (mViewerConfigReader != null) {
- // Load in background to avoid delay in boot process.
- // The caveat is that any log message that is also logged to logcat will not be
- // successfully decoded until this completes.
- mBackgroundLoggingService.execute(() -> mViewerConfigReader
- .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
}
}
@@ -403,6 +291,10 @@
}
}
+ protected void readyToLogToLogcat() {
+ mLogcatReady = true;
+ }
+
/**
* Responds to a shell command.
*/
@@ -499,57 +391,21 @@
}
@Deprecated
- private void dumpViewerConfig() {
- if (mViewerConfigInputStreamProvider == null) {
- // No viewer config available
+ abstract void dumpViewerConfig();
+
+ @NonNull
+ abstract String getLogcatMessageString(@NonNull Message message);
+
+ private void logToLogcat(@NonNull String tag, @NonNull LogLevel level, @NonNull Message message,
+ @Nullable Object[] args) {
+ if (!mLogcatReady) {
+ Log.w(LOG_TAG, "Trying to log a protolog message with hash "
+ + message.getMessageHash() + " to logcat before the service is ready to accept "
+ + "such requests.");
return;
}
- Log.d(LOG_TAG, "Dumping viewer config to trace");
-
- Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
-
- Log.d(LOG_TAG, "Dumped viewer config to trace");
- }
-
- private void logToLogcat(String tag, LogLevel level, Message message,
- @Nullable Object[] args) {
- String messageString;
- if (mViewerConfigReader == null) {
- messageString = message.getMessage();
-
- if (messageString == null) {
- Log.e(LOG_TAG, "Failed to decode message for logcat. "
- + "Message not available without ViewerConfig to decode the hash.");
- }
- } else {
- messageString = message.getMessage(mViewerConfigReader);
-
- if (messageString == null) {
- Log.e(LOG_TAG, "Failed to decode message for logcat. "
- + "Message hash either not available in viewerConfig file or "
- + "not loaded into memory from file before decoding.");
- }
- }
-
- if (messageString == null) {
- StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
- if (args != null) {
- builder.append(" args = (");
- builder.append(String.join(", ", Arrays.stream(args)
- .map(it -> {
- if (it == null) {
- return "null";
- } else {
- return it.toString();
- }
- }).toList()));
- builder.append(")");
- }
- messageString = builder.toString();
- args = new Object[0];
- }
-
+ String messageString = getLogcatMessageString(message);
logToLogcat(tag, level, messageString, args);
}
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
new file mode 100644
index 0000000..febe1f3
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -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.android.internal.protolog;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
+ private static final String LOG_TAG = "PerfettoProtoLogImpl";
+
+ @NonNull
+ private final ProtoLogViewerConfigReader mViewerConfigReader;
+ @Deprecated
+ @NonNull
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ @NonNull
+ private final String mViewerConfigFilePath;
+
+ public ProcessedPerfettoProtoLogImpl(
+ @NonNull String viewerConfigFilePath,
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+ this(viewerConfigFilePath, new ViewerConfigInputStreamProvider() {
+ @NonNull
+ @Override
+ public AutoClosableProtoInputStream getInputStream() {
+ try {
+ final var protoFileInputStream =
+ new FileInputStream(viewerConfigFilePath);
+ return new AutoClosableProtoInputStream(protoFileInputStream);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(
+ "Failed to load viewer config file " + viewerConfigFilePath, e);
+ }
+ }
+ },
+ cacheUpdater, groups);
+ }
+
+ @VisibleForTesting
+ public ProcessedPerfettoProtoLogImpl(
+ @NonNull String viewerConfigFilePath,
+ @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+ super(cacheUpdater, groups);
+
+ this.mViewerConfigFilePath = viewerConfigFilePath;
+
+ this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
+ this.mViewerConfigReader = new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider);
+
+ loadLogcatGroupsViewerConfig(groups);
+ }
+
+ @VisibleForTesting
+ public ProcessedPerfettoProtoLogImpl(
+ @NonNull String viewerConfigFilePath,
+ @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @NonNull ProtoLogViewerConfigReader viewerConfigReader,
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups,
+ @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
+ @Nullable IProtoLogConfigurationService configurationService)
+ throws ServiceManager.ServiceNotFoundException {
+ super(cacheUpdater, groups, dataSourceBuilder, configurationService);
+
+ this.mViewerConfigFilePath = viewerConfigFilePath;
+
+ this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
+ this.mViewerConfigReader = viewerConfigReader;
+
+ loadLogcatGroupsViewerConfig(groups);
+ }
+
+ @NonNull
+ @Override
+ protected RegisterClientArgs createConfigurationServiceRegisterClientArgs() {
+ return new RegisterClientArgs()
+ .setViewerConfigFile(mViewerConfigFilePath);
+ }
+
+ /**
+ * Start text logging
+ * @param groups Groups to start text logging for
+ * @param logger A logger to write status updates to
+ * @return status code
+ */
+ @Override
+ public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ mViewerConfigReader.loadViewerConfig(groups, logger);
+ return super.startLoggingToLogcat(groups, logger);
+ }
+
+ /**
+ * Stop text logging
+ * @param groups Groups to start text logging for
+ * @param logger A logger to write status updates to
+ * @return status code
+ */
+ @Override
+ public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ mViewerConfigReader.unloadViewerConfig(groups, logger);
+ return super.stopLoggingToLogcat(groups, logger);
+ }
+
+ @Deprecated
+ @Override
+ void dumpViewerConfig() {
+ Log.d(LOG_TAG, "Dumping viewer config to trace");
+ Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
+ Log.d(LOG_TAG, "Dumped viewer config to trace");
+ }
+
+ @NonNull
+ @Override
+ String getLogcatMessageString(@NonNull Message message) {
+ String messageString;
+ messageString = message.getMessage(mViewerConfigReader);
+
+ if (messageString == null) {
+ throw new RuntimeException("Failed to decode message for logcat. "
+ + "Message hash (" + message.getMessageHash() + ") either not available in "
+ + "viewerConfig file (" + mViewerConfigFilePath + ") or "
+ + "not loaded into memory from file before decoding.");
+ }
+
+ return messageString;
+ }
+
+ private void loadLogcatGroupsViewerConfig(@NonNull IProtoLogGroup[] protoLogGroups) {
+ final var groupsLoggingToLogcat = new ArrayList<String>();
+ for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+ if (protoLogGroup.isLogToLogcat()) {
+ groupsLoggingToLogcat.add(protoLogGroup.name());
+ }
+ }
+
+ // Load in background to avoid delay in boot process.
+ // The caveat is that any log message that is also logged to logcat will not be
+ // successfully decoded until this completes.
+ mBackgroundLoggingService.execute(() -> {
+ mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0]));
+ readyToLogToLogcat();
+ });
+ }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 60213b1..d117e93 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -70,16 +70,16 @@
// directly to the generated tracing implementations.
if (android.tracing.Flags.perfettoProtologTracing()) {
synchronized (sInitLock) {
+ final var allGroups = new HashSet<>(Arrays.stream(groups).toList());
if (sProtoLogInstance != null) {
// The ProtoLog instance has already been initialized in this process
final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
- final var allGroups = new HashSet<>(alreadyRegisteredGroups);
- allGroups.addAll(Arrays.stream(groups).toList());
- groups = allGroups.toArray(new IProtoLogGroup[0]);
+ allGroups.addAll(alreadyRegisteredGroups);
}
try {
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ sProtoLogInstance = new UnprocessedPerfettoProtoLogImpl(
+ allGroups.toArray(new IProtoLogGroup[0]));
} catch (ServiceManager.ServiceNotFoundException e) {
throw new RuntimeException(e);
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
index 8d37899..e9a8770 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -379,7 +379,7 @@
@NonNull String viewerConfigFilePath) {
Utils.dumpViewerConfig(dataSource, () -> {
try {
- return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+ return new AutoClosableProtoInputStream(new FileInputStream(viewerConfigFilePath));
} catch (FileNotFoundException e) {
throw new RuntimeException(
"Failed to load viewer config file " + viewerConfigFilePath, e);
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 5d67534..3378d08 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -105,31 +105,10 @@
+ "viewerConfigPath = " + sViewerConfigPath);
final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
-
if (android.tracing.Flags.perfettoProtologTracing()) {
- try {
- File f = new File(sViewerConfigPath);
- if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
- // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
- // In some tests the viewer config file might not exist in which we don't
- // want to provide config path to the user
- Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
- + ProtoLogImpl.class.getSimpleName() + ". "
- + "Setting up without a viewer config instead...");
-
- sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
- } else {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
- }
- } catch (ServiceManager.ServiceNotFoundException e) {
- throw new RuntimeException(e);
- }
+ sServiceInstance = createProtoLogImpl(groups);
} else {
- var protologImpl = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
- protologImpl.registerGroups(groups);
- sServiceInstance = protologImpl;
+ sServiceInstance = createLegacyProtoLogImpl(groups);
}
sCacheUpdater.run();
@@ -137,6 +116,34 @@
return sServiceInstance;
}
+ private static IProtoLog createProtoLogImpl(IProtoLogGroup[] groups) {
+ try {
+ File f = new File(sViewerConfigPath);
+ if (!f.exists()) {
+ // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+ // In robolectric tests the viewer config file isn't current available, so we cannot
+ // use the ProcessedPerfettoProtoLogImpl.
+ Log.e(LOG_TAG, "Failed to find viewer config file " + sViewerConfigPath
+ + " when setting up " + ProtoLogImpl.class.getSimpleName() + ". "
+ + "ProtoLog will not work here!");
+
+ return new NoViewerConfigProtoLogImpl();
+ } else {
+ return new ProcessedPerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ }
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static LegacyProtoLogImpl createLegacyProtoLogImpl(IProtoLogGroup[] groups) {
+ var protologImpl = new LegacyProtoLogImpl(
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+ protologImpl.registerGroups(groups);
+
+ return protologImpl;
+ }
+
@VisibleForTesting
public static synchronized void setSingleInstance(@Nullable IProtoLog instance) {
sServiceInstance = instance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 571fe0b..524f642 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -106,46 +106,47 @@
long targetGroupId = loadGroupId(group);
final Map<Long, String> hashesForGroup = new TreeMap<>();
- final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+ try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
+ final var pis = pisWrapper.get();
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) MESSAGES) {
+ final long inMessageToken = pis.start(MESSAGES);
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (pis.getFieldNumber() == (int) MESSAGES) {
- final long inMessageToken = pis.start(MESSAGES);
-
- long messageId = 0;
- String message = null;
- int groupId = 0;
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) MESSAGE_ID:
- messageId = pis.readLong(MESSAGE_ID);
- break;
- case (int) MESSAGE:
- message = pis.readString(MESSAGE);
- break;
- case (int) GROUP_ID:
- groupId = pis.readInt(GROUP_ID);
- break;
+ long messageId = 0;
+ String message = null;
+ int groupId = 0;
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) MESSAGE_ID:
+ messageId = pis.readLong(MESSAGE_ID);
+ break;
+ case (int) MESSAGE:
+ message = pis.readString(MESSAGE);
+ break;
+ case (int) GROUP_ID:
+ groupId = pis.readInt(GROUP_ID);
+ break;
+ }
}
- }
- if (groupId == 0) {
- throw new IOException("Failed to get group id");
- }
+ if (groupId == 0) {
+ throw new IOException("Failed to get group id");
+ }
- if (messageId == 0) {
- throw new IOException("Failed to get message id");
- }
+ if (messageId == 0) {
+ throw new IOException("Failed to get message id");
+ }
- if (message == null) {
- throw new IOException("Failed to get message string");
- }
+ if (message == null) {
+ throw new IOException("Failed to get message string");
+ }
- if (groupId == targetGroupId) {
- hashesForGroup.put(messageId, message);
- }
+ if (groupId == targetGroupId) {
+ hashesForGroup.put(messageId, message);
+ }
- pis.end(inMessageToken);
+ pis.end(inMessageToken);
+ }
}
}
@@ -153,30 +154,32 @@
}
private long loadGroupId(@NonNull String group) throws IOException {
- final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+ try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
+ final var pis = pisWrapper.get();
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (pis.getFieldNumber() == (int) GROUPS) {
- final long inMessageToken = pis.start(GROUPS);
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) GROUPS) {
+ final long inMessageToken = pis.start(GROUPS);
- long groupId = 0;
- String groupName = null;
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) ID:
- groupId = pis.readInt(ID);
- break;
- case (int) NAME:
- groupName = pis.readString(NAME);
- break;
+ long groupId = 0;
+ String groupName = null;
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID:
+ groupId = pis.readInt(ID);
+ break;
+ case (int) NAME:
+ groupName = pis.readString(NAME);
+ break;
+ }
}
- }
- if (Objects.equals(groupName, group)) {
- return groupId;
- }
+ if (Objects.equals(groupName, group)) {
+ return groupId;
+ }
- pis.end(inMessageToken);
+ pis.end(inMessageToken);
+ }
}
}
diff --git a/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
new file mode 100644
index 0000000..f3fe580
--- /dev/null
+++ b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.protolog;
+
+import android.annotation.NonNull;
+import android.os.ServiceManager;
+
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+public class UnprocessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
+ public UnprocessedPerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
+ this(() -> {}, groups);
+ }
+
+ public UnprocessedPerfettoProtoLogImpl(@NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
+ super(cacheUpdater, groups);
+ readyToLogToLogcat();
+ }
+
+ @NonNull
+ @Override
+ protected RegisterClientArgs createConfigurationServiceRegisterClientArgs() {
+ return new RegisterClientArgs();
+ }
+
+ @Override
+ void dumpViewerConfig() {
+ // No-op
+ }
+
+ @NonNull
+ @Override
+ String getLogcatMessageString(@NonNull Message message) {
+ String messageString;
+ messageString = message.getMessage();
+
+ if (messageString == null) {
+ throw new RuntimeException("Failed to decode message for logcat. "
+ + "Message not available without ViewerConfig to decode the hash.");
+ }
+
+ return messageString;
+ }
+}
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 00ef80a..629682c 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -48,8 +48,8 @@
public static void dumpViewerConfig(@NonNull ProtoLogDataSource dataSource,
@NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
dataSource.trace(ctx -> {
- try {
- ProtoInputStream pis = viewerConfigInputStreamProvider.getInputStream();
+ try (var pisWrapper = viewerConfigInputStreamProvider.getInputStream()) {
+ final var pis = pisWrapper.get();
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
index 14bc8e4..60c9892 100644
--- a/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
+++ b/core/java/com/android/internal/protolog/ViewerConfigInputStreamProvider.java
@@ -17,12 +17,12 @@
package com.android.internal.protolog;
import android.annotation.NonNull;
-import android.util.proto.ProtoInputStream;
public interface ViewerConfigInputStreamProvider {
/**
* @return a ProtoInputStream.
*/
@NonNull
- ProtoInputStream getInputStream();
+ AutoClosableProtoInputStream getInputStream();
}
+
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 81b885a..b5c87868 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -84,4 +84,5 @@
void onSimultaneousCallingStateChanged(in int[] subIds);
void onCarrierRoamingNtnModeChanged(in boolean active);
void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
+ void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index f836cf2..ca75abd 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -123,4 +123,5 @@
void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason);
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
+ void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4198171..9c92e5c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7175,4 +7175,10 @@
<string name="identity_check_settings_action"></string>
<!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] -->
<string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string>
+
+ <!-- The name of the service for forensic backup transport. -->
+ <string name="config_forensicBackupTransport" translatable="false"></string>
+
+ <!-- Whether to enable fp unlock when screen turns off on udfps devices -->
+ <bool name="config_screen_off_udfps_enabled">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0b2b345..712b994 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5636,4 +5636,10 @@
<!-- Identity check strings -->
<java-symbol type="string" name="identity_check_settings_action" />
<java-symbol type="string" name="identity_check_settings_package_name" />
+
+ <!-- Forensic backup transport -->
+ <java-symbol type="string" name="config_forensicBackupTransport" />
+
+ <!-- Fingerprint screen off unlock config -->
+ <java-symbol type="bool" name="config_screen_off_udfps_enabled" />
</resources>
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 04945f3..9099918e 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -71,8 +71,7 @@
VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
assertFalse(noCapabilities.hasFrequencyControl());
VibratorInfo composeAndFrequencyControl = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setCapabilities(
- IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.build();
assertTrue(composeAndFrequencyControl.hasFrequencyControl());
}
@@ -153,7 +152,8 @@
VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
assertFalse(noCapabilities.areEnvelopeEffectsSupported());
VibratorInfo envelopeEffectCapability = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)
+ .setCapabilities(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)
.build();
assertTrue(envelopeEffectCapability.areEnvelopeEffectsSupported());
}
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 4edf52b..0964aab 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -53,22 +53,9 @@
name: "use_var_font",
}
-soong_config_module_type {
- name: "prebuilt_fonts_xml",
- module_type: "prebuilt_etc",
- config_namespace: "noto_sans_cjk_config",
- bool_variables: ["use_var_font"],
- properties: ["src"],
-}
-
-prebuilt_fonts_xml {
+prebuilt_etc {
name: "fonts.xml",
src: "fonts.xml",
- soong_config_variables: {
- use_var_font: {
- src: "fonts_cjkvf.xml",
- },
- },
}
prebuilt_etc {
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
deleted file mode 100644
index ae50da1..0000000
--- a/data/fonts/font_fallback.xml
+++ /dev/null
@@ -1,950 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- In this file, all fonts without names are added to the default list.
- Fonts are chosen based on a match: full BCP-47 language tag including
- script, then just language, and finally order (the first font containing
- the glyph).
-
- Order of appearance is also the tiebreaker for weight matching. This is
- the reason why the 900 weights of Roboto precede the 700 weights - we
- prefer the former when an 800 weight is requested. Since bold spans
- effectively add 300 to the weight, this ensures that 900 is the bold
- paired with the 500 weight, ensuring adequate contrast.
-
-
- The font_fallback.xml defines the list of font used by the system.
-
- `familyset` node:
- A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
- to `familyset` node.
- The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
-
- `family` node:
- A `family` node defines a single font family definition.
- A font family is a set of fonts for drawing text in various styles such as weight, slant.
- There are three types of families, default family, named family and locale fallback family.
-
- The default family is a special family node appeared the first node of the `familyset` node.
- The default family is used as first priority fallback.
- Only `name` attribute can be used for default family node. If the `name` attribute is
- specified, This family will also works as named family.
-
- The named family is a family that has name attribute. The named family defines a new fallback.
- For example, if the name attribute is "serif", it creates serif fallback. Developers can
- access the fallback by using Typeface#create API.
- The named family can not have attribute other than `name` attribute. The `name` attribute
- cannot be empty.
-
- The locale fallback family is a font family that is used for fallback. The fallback family is
- used when the named family or default family cannot be used. The locale fallback family can
- have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
- separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
- `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
-
- `alias` node:
- An `alias` node defines a alias of named family with changing weight offset. An `alias` node
- can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
- defines new fallback that has the name of specified `name` attribute. The fallback list is
- the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
- is specified, the base weight offset is shifted to the specified value. For example, if the
- `weight` is 500, the output text is drawn with 500 of weight.
-
- `font` node:
- A `font` node defines a single font definition. There are two types of fonts, static font and
- variable font.
-
- A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
- is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
- valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
- attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
- index of the font collection. If this is not specified, it is treated as 0. If the font file
- is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
- optional attribute. A PostScript name is used for identifying target of system font update.
- If this is not specified, the system assumes the filename is same to PostScript name of the
- font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
- PostScript name of this font is "Roboto-Regular".
-
- A variable font can be only defined for the variable font file. A variable font can have
- `axis` child nodes for specifying axis values. A variable font can have all attribute of
- static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
- is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
- be specified.
-
- If `supportedAxes` attribute is not specified, this `font` node works as static font of the
- single instance of variable font specified with `axis` children.
-
- If `supportedAxes` attribute is specified, the system dynamically create font instance for the
- given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
- attribute and `axis` child that has `wght` tag become optional and ignored because it is
- determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
- attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
- ignored.
-
- `axis` node:
- An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
- attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
- specified in `supportedAxes` attribute, the style value works like a default instance.
--->
-<familyset>
- <!-- first font is default -->
- <family name="sans-serif">
- <font supportedAxes="wght,ital">Roboto-Regular.ttf
- <axis tag="wdth" stylevalue="100" />
- </font>
- </family>
-
-
- <!-- Note that aliases must come after the fonts they reference. -->
- <alias name="sans-serif-thin" to="sans-serif" weight="100" />
- <alias name="sans-serif-light" to="sans-serif" weight="300" />
- <alias name="sans-serif-medium" to="sans-serif" weight="500" />
- <alias name="sans-serif-black" to="sans-serif" weight="900" />
- <alias name="arial" to="sans-serif" />
- <alias name="helvetica" to="sans-serif" />
- <alias name="tahoma" to="sans-serif" />
- <alias name="verdana" to="sans-serif" />
-
- <family name="sans-serif-condensed">
- <font supportedAxes="wght,ital">Roboto-Regular.ttf
- <axis tag="wdth" stylevalue="75" />
- </font>
- </family>
- <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
- <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
- <family name="serif">
- <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
- <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
- <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
- <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
- </family>
- <alias name="serif-bold" to="serif" weight="700" />
- <alias name="times" to="serif" />
- <alias name="times new roman" to="serif" />
- <alias name="palatino" to="serif" />
- <alias name="georgia" to="serif" />
- <alias name="baskerville" to="serif" />
- <alias name="goudy" to="serif" />
- <alias name="fantasy" to="serif" />
- <alias name="ITC Stone Serif" to="serif" />
-
- <family name="monospace">
- <font weight="400" style="normal">DroidSansMono.ttf</font>
- </family>
- <alias name="sans-serif-monospace" to="monospace" />
- <alias name="monaco" to="monospace" />
-
- <family name="serif-monospace">
- <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
- </family>
- <alias name="courier" to="serif-monospace" />
- <alias name="courier new" to="serif-monospace" />
-
- <family name="casual">
- <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
- </family>
-
- <family name="cursive">
- <font supportedAxes="wght">DancingScript-Regular.ttf</font>
- </family>
-
- <family name="sans-serif-smallcaps">
- <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
- </family>
-
- <family name="source-sans-pro">
- <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
- <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
- <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
- <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
- <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
- <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
- </family>
- <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
- <family name="roboto-flex">
- <font supportedAxes="wght">RobotoFlex-Regular.ttf
- <axis tag="wdth" stylevalue="100" />
- </font>
- </family>
-
- <!-- fallback fonts -->
- <family lang="und-Arab" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
- NotoNaskhArabic-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
- </family>
- <family lang="und-Arab" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
- NotoNaskhArabicUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
- </family>
- <family lang="und-Ethi">
- <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
- NotoSansEthiopic-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
- NotoSerifEthiopic-VF.ttf
- </font>
- </family>
- <family lang="und-Hebr">
- <font weight="400" style="normal" postScriptName="NotoSansHebrew">
- NotoSansHebrew-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifThai-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
- NotoSansThaiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
- </family>
- <family lang="und-Armn">
- <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
- NotoSansArmenian-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
- NotoSerifArmenian-VF.ttf
- </font>
- </family>
- <family lang="und-Geor,und-Geok">
- <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
- NotoSansGeorgian-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
- NotoSerifGeorgian-VF.ttf
- </font>
- </family>
- <family lang="und-Deva" variant="elegant">
- <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
- NotoSansDevanagari-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
- NotoSerifDevanagari-VF.ttf
- </font>
- </family>
- <family lang="und-Deva" variant="compact">
- <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
- NotoSansDevanagariUI-VF.ttf
- </font>
- </family>
-
- <!-- All scripts of India should come after Devanagari, due to shared
- danda characters.
- -->
- <family lang="und-Gujr" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansGujarati">
- NotoSansGujarati-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
- supportedAxes="wght">
- NotoSerifGujarati-VF.ttf
- </font>
- </family>
- <family lang="und-Gujr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
- NotoSansGujaratiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
- </family>
- <family lang="und-Guru" variant="elegant">
- <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
- NotoSansGurmukhi-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
- NotoSerifGurmukhi-VF.ttf
- </font>
- </family>
- <family lang="und-Guru" variant="compact">
- <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
- NotoSansGurmukhiUI-VF.ttf
- </font>
- </family>
- <family lang="und-Taml" variant="elegant">
- <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
- NotoSansTamil-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
- NotoSerifTamil-VF.ttf
- </font>
- </family>
- <family lang="und-Taml" variant="compact">
- <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
- NotoSansTamilUI-VF.ttf
- </font>
- </family>
- <family lang="und-Mlym" variant="elegant">
- <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
- NotoSansMalayalam-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
- NotoSerifMalayalam-VF.ttf
- </font>
- </family>
- <family lang="und-Mlym" variant="compact">
- <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
- NotoSansMalayalamUI-VF.ttf
- </font>
- </family>
- <family lang="und-Beng" variant="elegant">
- <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
- NotoSansBengali-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
- NotoSerifBengali-VF.ttf
- </font>
- </family>
- <family lang="und-Beng" variant="compact">
- <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
- NotoSansBengaliUI-VF.ttf
- </font>
- </family>
- <family lang="und-Telu" variant="elegant">
- <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
- NotoSansTelugu-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
- NotoSerifTelugu-VF.ttf
- </font>
- </family>
- <family lang="und-Telu" variant="compact">
- <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
- NotoSansTeluguUI-VF.ttf
- </font>
- </family>
- <family lang="und-Knda" variant="elegant">
- <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
- NotoSansKannada-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
- NotoSerifKannada-VF.ttf
- </font>
- </family>
- <family lang="und-Knda" variant="compact">
- <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
- NotoSansKannadaUI-VF.ttf
- </font>
- </family>
- <family lang="und-Orya" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
- </family>
- <family lang="und-Orya" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
- NotoSansOriyaUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
- </family>
- <family lang="und-Sinh" variant="elegant">
- <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
- NotoSansSinhala-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
- NotoSerifSinhala-VF.ttf
- </font>
- </family>
- <family lang="und-Sinh" variant="compact">
- <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
- NotoSansSinhalaUI-VF.ttf
- </font>
- </family>
- <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
- <family lang="und-Khmr" variant="elegant">
- <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="26.0"/>
- </font>
- <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="39.0"/>
- </font>
- <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="58.0"/>
- </font>
- <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="90.0"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="108.0"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="128.0"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="151.0"/>
- </font>
- <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="169.0"/>
- </font>
- <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="190.0"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
- </family>
- <family lang="und-Khmr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
- NotoSansKhmerUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="elegant">
- <font weight="400" style="normal">NotoSansLao-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifLao-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
- </family>
- <family lang="und-Mymr" variant="elegant">
- <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
- </family>
- <family lang="und-Mymr" variant="compact">
- <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
- </family>
- <family lang="und-Thaa">
- <font weight="400" style="normal" postScriptName="NotoSansThaana">
- NotoSansThaana-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
- </family>
- <family lang="und-Cham">
- <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
- </family>
- <family lang="und-Ahom">
- <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
- </family>
- <family lang="und-Adlm">
- <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
- NotoSansAdlam-VF.ttf
- </font>
- </family>
- <family lang="und-Avst">
- <font weight="400" style="normal" postScriptName="NotoSansAvestan">
- NotoSansAvestan-Regular.ttf
- </font>
- </family>
- <family lang="und-Bali">
- <font weight="400" style="normal" postScriptName="NotoSansBalinese">
- NotoSansBalinese-Regular.ttf
- </font>
- </family>
- <family lang="und-Bamu">
- <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
- </font>
- </family>
- <family lang="und-Batk">
- <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
- </font>
- </family>
- <family lang="und-Brah">
- <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
- NotoSansBrahmi-Regular.ttf
- </font>
- </family>
- <family lang="und-Bugi">
- <font weight="400" style="normal" postScriptName="NotoSansBuginese">
- NotoSansBuginese-Regular.ttf
- </font>
- </family>
- <family lang="und-Buhd">
- <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
- </font>
- </family>
- <family lang="und-Cans">
- <font weight="400" style="normal">
- NotoSansCanadianAboriginal-Regular.ttf
- </font>
- </family>
- <family lang="und-Cari">
- <font weight="400" style="normal" postScriptName="NotoSansCarian">
- NotoSansCarian-Regular.ttf
- </font>
- </family>
- <family lang="und-Cakm">
- <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
- </family>
- <family lang="und-Cher">
- <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
- </family>
- <family lang="und-Copt">
- <font weight="400" style="normal" postScriptName="NotoSansCoptic">
- NotoSansCoptic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xsux">
- <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
- NotoSansCuneiform-Regular.ttf
- </font>
- </family>
- <family lang="und-Cprt">
- <font weight="400" style="normal" postScriptName="NotoSansCypriot">
- NotoSansCypriot-Regular.ttf
- </font>
- </family>
- <family lang="und-Dsrt">
- <font weight="400" style="normal" postScriptName="NotoSansDeseret">
- NotoSansDeseret-Regular.ttf
- </font>
- </family>
- <family lang="und-Egyp">
- <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
- NotoSansEgyptianHieroglyphs-Regular.ttf
- </font>
- </family>
- <family lang="und-Elba">
- <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
- </family>
- <family lang="und-Glag">
- <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
- NotoSansGlagolitic-Regular.ttf
- </font>
- </family>
- <family lang="und-Goth">
- <font weight="400" style="normal" postScriptName="NotoSansGothic">
- NotoSansGothic-Regular.ttf
- </font>
- </family>
- <family lang="und-Hano">
- <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
- NotoSansHanunoo-Regular.ttf
- </font>
- </family>
- <family lang="und-Armi">
- <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
- NotoSansImperialAramaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Phli">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
- NotoSansInscriptionalPahlavi-Regular.ttf
- </font>
- </family>
- <family lang="und-Prti">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
- NotoSansInscriptionalParthian-Regular.ttf
- </font>
- </family>
- <family lang="und-Java">
- <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
- </family>
- <family lang="und-Kthi">
- <font weight="400" style="normal" postScriptName="NotoSansKaithi">
- NotoSansKaithi-Regular.ttf
- </font>
- </family>
- <family lang="und-Kali">
- <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
- NotoSansKayahLi-Regular.ttf
- </font>
- </family>
- <family lang="und-Khar">
- <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
- NotoSansKharoshthi-Regular.ttf
- </font>
- </family>
- <family lang="und-Lepc">
- <font weight="400" style="normal" postScriptName="NotoSansLepcha">
- NotoSansLepcha-Regular.ttf
- </font>
- </family>
- <family lang="und-Limb">
- <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
- </font>
- </family>
- <family lang="und-Linb">
- <font weight="400" style="normal" postScriptName="NotoSansLinearB">
- NotoSansLinearB-Regular.ttf
- </font>
- </family>
- <family lang="und-Lisu">
- <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
- </font>
- </family>
- <family lang="und-Lyci">
- <font weight="400" style="normal" postScriptName="NotoSansLycian">
- NotoSansLycian-Regular.ttf
- </font>
- </family>
- <family lang="und-Lydi">
- <font weight="400" style="normal" postScriptName="NotoSansLydian">
- NotoSansLydian-Regular.ttf
- </font>
- </family>
- <family lang="und-Mand">
- <font weight="400" style="normal" postScriptName="NotoSansMandaic">
- NotoSansMandaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Mtei">
- <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
- NotoSansMeeteiMayek-Regular.ttf
- </font>
- </family>
- <family lang="und-Talu">
- <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
- NotoSansNewTaiLue-Regular.ttf
- </font>
- </family>
- <family lang="und-Nkoo">
- <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
- </font>
- </family>
- <family lang="und-Ogam">
- <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
- </font>
- </family>
- <family lang="und-Olck">
- <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
- NotoSansOlChiki-Regular.ttf
- </font>
- </family>
- <family lang="und-Ital">
- <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
- NotoSansOldItalic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xpeo">
- <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
- NotoSansOldPersian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sarb">
- <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
- NotoSansOldSouthArabian-Regular.ttf
- </font>
- </family>
- <family lang="und-Orkh">
- <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
- NotoSansOldTurkic-Regular.ttf
- </font>
- </family>
- <family lang="und-Osge">
- <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
- </family>
- <family lang="und-Osma">
- <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
- NotoSansOsmanya-Regular.ttf
- </font>
- </family>
- <family lang="und-Phnx">
- <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
- NotoSansPhoenician-Regular.ttf
- </font>
- </family>
- <family lang="und-Rjng">
- <font weight="400" style="normal" postScriptName="NotoSansRejang">
- NotoSansRejang-Regular.ttf
- </font>
- </family>
- <family lang="und-Runr">
- <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
- </font>
- </family>
- <family lang="und-Samr">
- <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
- NotoSansSamaritan-Regular.ttf
- </font>
- </family>
- <family lang="und-Saur">
- <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
- NotoSansSaurashtra-Regular.ttf
- </font>
- </family>
- <family lang="und-Shaw">
- <font weight="400" style="normal" postScriptName="NotoSansShavian">
- NotoSansShavian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sund">
- <font weight="400" style="normal" postScriptName="NotoSansSundanese">
- NotoSansSundanese-Regular.ttf
- </font>
- </family>
- <family lang="und-Sylo">
- <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
- NotoSansSylotiNagri-Regular.ttf
- </font>
- </family>
- <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
- <family lang="und-Syre">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
- NotoSansSyriacEstrangela-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrn">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
- NotoSansSyriacEastern-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrj">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
- NotoSansSyriacWestern-Regular.ttf
- </font>
- </family>
- <family lang="und-Tglg">
- <font weight="400" style="normal" postScriptName="NotoSansTagalog">
- NotoSansTagalog-Regular.ttf
- </font>
- </family>
- <family lang="und-Tagb">
- <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
- NotoSansTagbanwa-Regular.ttf
- </font>
- </family>
- <family lang="und-Lana">
- <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
- NotoSansTaiTham-Regular.ttf
- </font>
- </family>
- <family lang="und-Tavt">
- <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
- NotoSansTaiViet-Regular.ttf
- </font>
- </family>
- <family lang="und-Tibt">
- <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
- NotoSerifTibetan-VF.ttf
- </font>
- </family>
- <family lang="und-Tfng">
- <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
- </family>
- <family lang="und-Ugar">
- <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
- NotoSansUgaritic-Regular.ttf
- </font>
- </family>
- <family lang="und-Vaii">
- <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
- </font>
- </family>
- <family>
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
- </family>
- <family lang="zh-Hans">
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
- NotoSansCJK-Regular.ttc
- </font>
- <font weight="400" style="normal" index="2" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="zh-Hant,zh-Bopo">
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
- NotoSansCJK-Regular.ttc
- </font>
- <font weight="400" style="normal" index="3" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
- NotoSansCJK-Regular.ttc
- </font>
- <font weight="400" style="normal" index="0" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
- NotoSerifHentaigana.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- </family>
- <family lang="ko">
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
- NotoSansCJK-Regular.ttc
- </font>
- <font weight="400" style="normal" index="1" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmoji.ttf</font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
- </family>
- <family lang="und-Zsym">
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
- </family>
- <!--
- Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
- override the East Asian punctuation for Chinese.
- -->
- <family lang="und-Tale">
- <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
- </font>
- </family>
- <family lang="und-Yiii">
- <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
- </family>
- <family lang="und-Mong">
- <font weight="400" style="normal" postScriptName="NotoSansMongolian">
- NotoSansMongolian-Regular.ttf
- </font>
- </family>
- <family lang="und-Phag">
- <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
- NotoSansPhagsPa-Regular.ttf
- </font>
- </family>
- <family lang="und-Hluw">
- <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
- </family>
- <family lang="und-Bass">
- <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
- </family>
- <family lang="und-Bhks">
- <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
- </family>
- <family lang="und-Hatr">
- <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
- </family>
- <family lang="und-Lina">
- <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
- </family>
- <family lang="und-Mani">
- <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
- </family>
- <family lang="und-Marc">
- <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
- </family>
- <family lang="und-Merc">
- <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
- </family>
- <family lang="und-Plrd">
- <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
- </family>
- <family lang="und-Mroo">
- <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
- </family>
- <family lang="und-Mult">
- <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
- </family>
- <family lang="und-Nbat">
- <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
- </family>
- <family lang="und-Newa">
- <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
- </family>
- <family lang="und-Narb">
- <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
- </family>
- <family lang="und-Perm">
- <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
- </family>
- <family lang="und-Hmng">
- <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
- </family>
- <family lang="und-Palm">
- <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
- </family>
- <family lang="und-Pauc">
- <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
- </family>
- <family lang="und-Shrd">
- <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
- </family>
- <family lang="und-Sora">
- <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
- </family>
- <family lang="und-Gong">
- <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
- </family>
- <family lang="und-Rohg">
- <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
- </family>
- <family lang="und-Khoj">
- <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
- </family>
- <family lang="und-Gonm">
- <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
- </family>
- <family lang="und-Wcho">
- <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
- </family>
- <family lang="und-Wara">
- <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
- </family>
- <family lang="und-Gran">
- <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
- </family>
- <family lang="und-Modi">
- <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
- </family>
- <family lang="und-Dogr">
- <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
- </family>
- <family lang="und-Medf">
- <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
- NotoSansMedefaidrin-VF.ttf
- </font>
- </family>
- <family lang="und-Soyo">
- <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
- NotoSansSoyombo-VF.ttf
- </font>
- </family>
- <family lang="und-Takr">
- <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
- NotoSansTakri-VF.ttf
- </font>
- </family>
- <family lang="und-Hmnp">
- <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- </font>
- </family>
- <family lang="und-Yezi">
- <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
- NotoSerifYezidi-VF.ttf
- </font>
- </family>
-</familyset>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
deleted file mode 100644
index 407d704..0000000
--- a/data/fonts/font_fallback_cjkvf.xml
+++ /dev/null
@@ -1,966 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- In this file, all fonts without names are added to the default list.
- Fonts are chosen based on a match: full BCP-47 language tag including
- script, then just language, and finally order (the first font containing
- the glyph).
-
- Order of appearance is also the tiebreaker for weight matching. This is
- the reason why the 900 weights of Roboto precede the 700 weights - we
- prefer the former when an 800 weight is requested. Since bold spans
- effectively add 300 to the weight, this ensures that 900 is the bold
- paired with the 500 weight, ensuring adequate contrast.
-
-
- The font_fallback.xml defines the list of font used by the system.
-
- `familyset` node:
- A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
- to `familyset` node.
- The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
-
- `family` node:
- A `family` node defines a single font family definition.
- A font family is a set of fonts for drawing text in various styles such as weight, slant.
- There are three types of families, default family, named family and locale fallback family.
-
- The default family is a special family node appeared the first node of the `familyset` node.
- The default family is used as first priority fallback.
- Only `name` attribute can be used for default family node. If the `name` attribute is
- specified, This family will also works as named family.
-
- The named family is a family that has name attribute. The named family defines a new fallback.
- For example, if the name attribute is "serif", it creates serif fallback. Developers can
- access the fallback by using Typeface#create API.
- The named family can not have attribute other than `name` attribute. The `name` attribute
- cannot be empty.
-
- The locale fallback family is a font family that is used for fallback. The fallback family is
- used when the named family or default family cannot be used. The locale fallback family can
- have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
- separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
- `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
-
- `alias` node:
- An `alias` node defines a alias of named family with changing weight offset. An `alias` node
- can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
- defines new fallback that has the name of specified `name` attribute. The fallback list is
- the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
- is specified, the base weight offset is shifted to the specified value. For example, if the
- `weight` is 500, the output text is drawn with 500 of weight.
-
- `font` node:
- A `font` node defines a single font definition. There are two types of fonts, static font and
- variable font.
-
- A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
- is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
- valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
- attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
- index of the font collection. If this is not specified, it is treated as 0. If the font file
- is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
- optional attribute. A PostScript name is used for identifying target of system font update.
- If this is not specified, the system assumes the filename is same to PostScript name of the
- font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
- PostScript name of this font is "Roboto-Regular".
-
- A variable font can be only defined for the variable font file. A variable font can have
- `axis` child nodes for specifying axis values. A variable font can have all attribute of
- static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
- is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
- be specified.
-
- If `supportedAxes` attribute is not specified, this `font` node works as static font of the
- single instance of variable font specified with `axis` children.
-
- If `supportedAxes` attribute is specified, the system dynamically create font instance for the
- given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
- attribute and `axis` child that has `wght` tag become optional and ignored because it is
- determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
- attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
- ignored.
-
- `axis` node:
- An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
- attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
- specified in `supportedAxes` attribute, the style value works like a default instance.
--->
-<familyset>
- <!-- first font is default -->
- <family name="sans-serif">
- <font supportedAxes="wght,ital">Roboto-Regular.ttf
- <axis tag="wdth" stylevalue="100" />
- </font>
- </family>
-
-
- <!-- Note that aliases must come after the fonts they reference. -->
- <alias name="sans-serif-thin" to="sans-serif" weight="100" />
- <alias name="sans-serif-light" to="sans-serif" weight="300" />
- <alias name="sans-serif-medium" to="sans-serif" weight="500" />
- <alias name="sans-serif-black" to="sans-serif" weight="900" />
- <alias name="arial" to="sans-serif" />
- <alias name="helvetica" to="sans-serif" />
- <alias name="tahoma" to="sans-serif" />
- <alias name="verdana" to="sans-serif" />
-
- <family name="sans-serif-condensed">
- <font supportedAxes="wght,ital">Roboto-Regular.ttf
- <axis tag="wdth" stylevalue="75" />
- </font>
- </family>
- <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
- <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
- <family name="serif">
- <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
- <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
- <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
- <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
- </family>
- <alias name="serif-bold" to="serif" weight="700" />
- <alias name="times" to="serif" />
- <alias name="times new roman" to="serif" />
- <alias name="palatino" to="serif" />
- <alias name="georgia" to="serif" />
- <alias name="baskerville" to="serif" />
- <alias name="goudy" to="serif" />
- <alias name="fantasy" to="serif" />
- <alias name="ITC Stone Serif" to="serif" />
-
- <family name="monospace">
- <font weight="400" style="normal">DroidSansMono.ttf</font>
- </family>
- <alias name="sans-serif-monospace" to="monospace" />
- <alias name="monaco" to="monospace" />
-
- <family name="serif-monospace">
- <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
- </family>
- <alias name="courier" to="serif-monospace" />
- <alias name="courier new" to="serif-monospace" />
-
- <family name="casual">
- <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
- </family>
-
- <family name="cursive">
- <font supportedAxes="wght">DancingScript-Regular.ttf</font>
- </family>
-
- <family name="sans-serif-smallcaps">
- <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
- </family>
-
- <family name="source-sans-pro">
- <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
- <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
- <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
- <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
- <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
- <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
- </family>
- <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
- <family name="roboto-flex">
- <font supportedAxes="wght">RobotoFlex-Regular.ttf
- <axis tag="wdth" stylevalue="100" />
- </font>
- </family>
-
- <!-- fallback fonts -->
- <family lang="und-Arab" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
- NotoNaskhArabic-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
- </family>
- <family lang="und-Arab" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
- NotoNaskhArabicUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
- </family>
- <family lang="und-Ethi">
- <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
- NotoSansEthiopic-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
- NotoSerifEthiopic-VF.ttf
- </font>
- </family>
- <family lang="und-Hebr">
- <font weight="400" style="normal" postScriptName="NotoSansHebrew">
- NotoSansHebrew-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifThai-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
- NotoSansThaiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
- </family>
- <family lang="und-Armn">
- <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
- NotoSansArmenian-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
- NotoSerifArmenian-VF.ttf
- </font>
- </family>
- <family lang="und-Geor,und-Geok">
- <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
- NotoSansGeorgian-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
- NotoSerifGeorgian-VF.ttf
- </font>
- </family>
- <family lang="und-Deva" variant="elegant">
- <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
- NotoSansDevanagari-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
- NotoSerifDevanagari-VF.ttf
- </font>
- </family>
- <family lang="und-Deva" variant="compact">
- <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
- NotoSansDevanagariUI-VF.ttf
- </font>
- </family>
-
- <!-- All scripts of India should come after Devanagari, due to shared
- danda characters.
- -->
- <family lang="und-Gujr" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansGujarati">
- NotoSansGujarati-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
- supportedAxes="wght">
- NotoSerifGujarati-VF.ttf
- </font>
- </family>
- <family lang="und-Gujr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
- NotoSansGujaratiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
- </family>
- <family lang="und-Guru" variant="elegant">
- <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
- NotoSansGurmukhi-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
- NotoSerifGurmukhi-VF.ttf
- </font>
- </family>
- <family lang="und-Guru" variant="compact">
- <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
- NotoSansGurmukhiUI-VF.ttf
- </font>
- </family>
- <family lang="und-Taml" variant="elegant">
- <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
- NotoSansTamil-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
- NotoSerifTamil-VF.ttf
- </font>
- </family>
- <family lang="und-Taml" variant="compact">
- <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
- NotoSansTamilUI-VF.ttf
- </font>
- </family>
- <family lang="und-Mlym" variant="elegant">
- <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
- NotoSansMalayalam-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
- NotoSerifMalayalam-VF.ttf
- </font>
- </family>
- <family lang="und-Mlym" variant="compact">
- <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
- NotoSansMalayalamUI-VF.ttf
- </font>
- </family>
- <family lang="und-Beng" variant="elegant">
- <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
- NotoSansBengali-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
- NotoSerifBengali-VF.ttf
- </font>
- </family>
- <family lang="und-Beng" variant="compact">
- <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
- NotoSansBengaliUI-VF.ttf
- </font>
- </family>
- <family lang="und-Telu" variant="elegant">
- <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
- NotoSansTelugu-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
- NotoSerifTelugu-VF.ttf
- </font>
- </family>
- <family lang="und-Telu" variant="compact">
- <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
- NotoSansTeluguUI-VF.ttf
- </font>
- </family>
- <family lang="und-Knda" variant="elegant">
- <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
- NotoSansKannada-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
- NotoSerifKannada-VF.ttf
- </font>
- </family>
- <family lang="und-Knda" variant="compact">
- <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
- NotoSansKannadaUI-VF.ttf
- </font>
- </family>
- <family lang="und-Orya" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
- </family>
- <family lang="und-Orya" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
- NotoSansOriyaUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
- </family>
- <family lang="und-Sinh" variant="elegant">
- <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
- NotoSansSinhala-VF.ttf
- </font>
- <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
- NotoSerifSinhala-VF.ttf
- </font>
- </family>
- <family lang="und-Sinh" variant="compact">
- <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
- NotoSansSinhalaUI-VF.ttf
- </font>
- </family>
- <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
- <family lang="und-Khmr" variant="elegant">
- <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="26.0"/>
- </font>
- <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="39.0"/>
- </font>
- <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="58.0"/>
- </font>
- <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="90.0"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="108.0"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="128.0"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="151.0"/>
- </font>
- <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="169.0"/>
- </font>
- <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="190.0"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
- </family>
- <family lang="und-Khmr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
- NotoSansKhmerUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="elegant">
- <font weight="400" style="normal">NotoSansLao-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifLao-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
- </family>
- <family lang="und-Mymr" variant="elegant">
- <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
- </family>
- <family lang="und-Mymr" variant="compact">
- <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
- </family>
- <family lang="und-Thaa">
- <font weight="400" style="normal" postScriptName="NotoSansThaana">
- NotoSansThaana-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
- </family>
- <family lang="und-Cham">
- <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
- </family>
- <family lang="und-Ahom">
- <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
- </family>
- <family lang="und-Adlm">
- <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
- NotoSansAdlam-VF.ttf
- </font>
- </family>
- <family lang="und-Avst">
- <font weight="400" style="normal" postScriptName="NotoSansAvestan">
- NotoSansAvestan-Regular.ttf
- </font>
- </family>
- <family lang="und-Bali">
- <font weight="400" style="normal" postScriptName="NotoSansBalinese">
- NotoSansBalinese-Regular.ttf
- </font>
- </family>
- <family lang="und-Bamu">
- <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
- </font>
- </family>
- <family lang="und-Batk">
- <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
- </font>
- </family>
- <family lang="und-Brah">
- <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
- NotoSansBrahmi-Regular.ttf
- </font>
- </family>
- <family lang="und-Bugi">
- <font weight="400" style="normal" postScriptName="NotoSansBuginese">
- NotoSansBuginese-Regular.ttf
- </font>
- </family>
- <family lang="und-Buhd">
- <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
- </font>
- </family>
- <family lang="und-Cans">
- <font weight="400" style="normal">
- NotoSansCanadianAboriginal-Regular.ttf
- </font>
- </family>
- <family lang="und-Cari">
- <font weight="400" style="normal" postScriptName="NotoSansCarian">
- NotoSansCarian-Regular.ttf
- </font>
- </family>
- <family lang="und-Cakm">
- <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
- </family>
- <family lang="und-Cher">
- <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
- </family>
- <family lang="und-Copt">
- <font weight="400" style="normal" postScriptName="NotoSansCoptic">
- NotoSansCoptic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xsux">
- <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
- NotoSansCuneiform-Regular.ttf
- </font>
- </family>
- <family lang="und-Cprt">
- <font weight="400" style="normal" postScriptName="NotoSansCypriot">
- NotoSansCypriot-Regular.ttf
- </font>
- </family>
- <family lang="und-Dsrt">
- <font weight="400" style="normal" postScriptName="NotoSansDeseret">
- NotoSansDeseret-Regular.ttf
- </font>
- </family>
- <family lang="und-Egyp">
- <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
- NotoSansEgyptianHieroglyphs-Regular.ttf
- </font>
- </family>
- <family lang="und-Elba">
- <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
- </family>
- <family lang="und-Glag">
- <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
- NotoSansGlagolitic-Regular.ttf
- </font>
- </family>
- <family lang="und-Goth">
- <font weight="400" style="normal" postScriptName="NotoSansGothic">
- NotoSansGothic-Regular.ttf
- </font>
- </family>
- <family lang="und-Hano">
- <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
- NotoSansHanunoo-Regular.ttf
- </font>
- </family>
- <family lang="und-Armi">
- <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
- NotoSansImperialAramaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Phli">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
- NotoSansInscriptionalPahlavi-Regular.ttf
- </font>
- </family>
- <family lang="und-Prti">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
- NotoSansInscriptionalParthian-Regular.ttf
- </font>
- </family>
- <family lang="und-Java">
- <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
- </family>
- <family lang="und-Kthi">
- <font weight="400" style="normal" postScriptName="NotoSansKaithi">
- NotoSansKaithi-Regular.ttf
- </font>
- </family>
- <family lang="und-Kali">
- <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
- NotoSansKayahLi-Regular.ttf
- </font>
- </family>
- <family lang="und-Khar">
- <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
- NotoSansKharoshthi-Regular.ttf
- </font>
- </family>
- <family lang="und-Lepc">
- <font weight="400" style="normal" postScriptName="NotoSansLepcha">
- NotoSansLepcha-Regular.ttf
- </font>
- </family>
- <family lang="und-Limb">
- <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
- </font>
- </family>
- <family lang="und-Linb">
- <font weight="400" style="normal" postScriptName="NotoSansLinearB">
- NotoSansLinearB-Regular.ttf
- </font>
- </family>
- <family lang="und-Lisu">
- <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
- </font>
- </family>
- <family lang="und-Lyci">
- <font weight="400" style="normal" postScriptName="NotoSansLycian">
- NotoSansLycian-Regular.ttf
- </font>
- </family>
- <family lang="und-Lydi">
- <font weight="400" style="normal" postScriptName="NotoSansLydian">
- NotoSansLydian-Regular.ttf
- </font>
- </family>
- <family lang="und-Mand">
- <font weight="400" style="normal" postScriptName="NotoSansMandaic">
- NotoSansMandaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Mtei">
- <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
- NotoSansMeeteiMayek-Regular.ttf
- </font>
- </family>
- <family lang="und-Talu">
- <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
- NotoSansNewTaiLue-Regular.ttf
- </font>
- </family>
- <family lang="und-Nkoo">
- <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
- </font>
- </family>
- <family lang="und-Ogam">
- <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
- </font>
- </family>
- <family lang="und-Olck">
- <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
- NotoSansOlChiki-Regular.ttf
- </font>
- </family>
- <family lang="und-Ital">
- <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
- NotoSansOldItalic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xpeo">
- <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
- NotoSansOldPersian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sarb">
- <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
- NotoSansOldSouthArabian-Regular.ttf
- </font>
- </family>
- <family lang="und-Orkh">
- <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
- NotoSansOldTurkic-Regular.ttf
- </font>
- </family>
- <family lang="und-Osge">
- <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
- </family>
- <family lang="und-Osma">
- <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
- NotoSansOsmanya-Regular.ttf
- </font>
- </family>
- <family lang="und-Phnx">
- <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
- NotoSansPhoenician-Regular.ttf
- </font>
- </family>
- <family lang="und-Rjng">
- <font weight="400" style="normal" postScriptName="NotoSansRejang">
- NotoSansRejang-Regular.ttf
- </font>
- </family>
- <family lang="und-Runr">
- <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
- </font>
- </family>
- <family lang="und-Samr">
- <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
- NotoSansSamaritan-Regular.ttf
- </font>
- </family>
- <family lang="und-Saur">
- <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
- NotoSansSaurashtra-Regular.ttf
- </font>
- </family>
- <family lang="und-Shaw">
- <font weight="400" style="normal" postScriptName="NotoSansShavian">
- NotoSansShavian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sund">
- <font weight="400" style="normal" postScriptName="NotoSansSundanese">
- NotoSansSundanese-Regular.ttf
- </font>
- </family>
- <family lang="und-Sylo">
- <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
- NotoSansSylotiNagri-Regular.ttf
- </font>
- </family>
- <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
- <family lang="und-Syre">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
- NotoSansSyriacEstrangela-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrn">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
- NotoSansSyriacEastern-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrj">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
- NotoSansSyriacWestern-Regular.ttf
- </font>
- </family>
- <family lang="und-Tglg">
- <font weight="400" style="normal" postScriptName="NotoSansTagalog">
- NotoSansTagalog-Regular.ttf
- </font>
- </family>
- <family lang="und-Tagb">
- <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
- NotoSansTagbanwa-Regular.ttf
- </font>
- </family>
- <family lang="und-Lana">
- <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
- NotoSansTaiTham-Regular.ttf
- </font>
- </family>
- <family lang="und-Tavt">
- <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
- NotoSansTaiViet-Regular.ttf
- </font>
- </family>
- <family lang="und-Tibt">
- <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
- NotoSerifTibetan-VF.ttf
- </font>
- </family>
- <family lang="und-Tfng">
- <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
- </family>
- <family lang="und-Ugar">
- <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
- NotoSansUgaritic-Regular.ttf
- </font>
- </family>
- <family lang="und-Vaii">
- <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
- </font>
- </family>
- <family>
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
- </family>
- <family lang="zh-Hans">
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"
- supportedAxes="wght">
- NotoSansCJK-Regular.ttc
- <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
- for making regular style as default. -->
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="400" style="normal" index="2" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="zh-Hant,zh-Bopo">
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"
- supportedAxes="wght">
- NotoSansCJK-Regular.ttc
- <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
- for making regular style as default. -->
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="400" style="normal" index="3" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"
- supportedAxes="wght">
- NotoSansCJK-Regular.ttc
- <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
- for making regular style as default. -->
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="400" style="normal" index="0" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
- NotoSerifHentaigana.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- </family>
- <family lang="ko">
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"
- supportedAxes="wght">
- NotoSansCJK-Regular.ttc
- <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
- for making regular style as default. -->
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="400" style="normal" index="1" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmoji.ttf</font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
- </family>
- <family lang="und-Zsym">
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
- </family>
- <!--
- Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
- override the East Asian punctuation for Chinese.
- -->
- <family lang="und-Tale">
- <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
- </font>
- </family>
- <family lang="und-Yiii">
- <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
- </family>
- <family lang="und-Mong">
- <font weight="400" style="normal" postScriptName="NotoSansMongolian">
- NotoSansMongolian-Regular.ttf
- </font>
- </family>
- <family lang="und-Phag">
- <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
- NotoSansPhagsPa-Regular.ttf
- </font>
- </family>
- <family lang="und-Hluw">
- <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
- </family>
- <family lang="und-Bass">
- <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
- </family>
- <family lang="und-Bhks">
- <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
- </family>
- <family lang="und-Hatr">
- <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
- </family>
- <family lang="und-Lina">
- <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
- </family>
- <family lang="und-Mani">
- <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
- </family>
- <family lang="und-Marc">
- <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
- </family>
- <family lang="und-Merc">
- <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
- </family>
- <family lang="und-Plrd">
- <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
- </family>
- <family lang="und-Mroo">
- <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
- </family>
- <family lang="und-Mult">
- <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
- </family>
- <family lang="und-Nbat">
- <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
- </family>
- <family lang="und-Newa">
- <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
- </family>
- <family lang="und-Narb">
- <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
- </family>
- <family lang="und-Perm">
- <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
- </family>
- <family lang="und-Hmng">
- <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
- </family>
- <family lang="und-Palm">
- <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
- </family>
- <family lang="und-Pauc">
- <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
- </family>
- <family lang="und-Shrd">
- <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
- </family>
- <family lang="und-Sora">
- <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
- </family>
- <family lang="und-Gong">
- <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
- </family>
- <family lang="und-Rohg">
- <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
- </family>
- <family lang="und-Khoj">
- <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
- </family>
- <family lang="und-Gonm">
- <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
- </family>
- <family lang="und-Wcho">
- <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
- </family>
- <family lang="und-Wara">
- <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
- </family>
- <family lang="und-Gran">
- <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
- </family>
- <family lang="und-Modi">
- <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
- </family>
- <family lang="und-Dogr">
- <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
- </family>
- <family lang="und-Medf">
- <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
- NotoSansMedefaidrin-VF.ttf
- </font>
- </family>
- <family lang="und-Soyo">
- <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
- NotoSansSoyombo-VF.ttf
- </font>
- </family>
- <family lang="und-Takr">
- <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
- NotoSansTakri-VF.ttf
- </font>
- </family>
- <family lang="und-Hmnp">
- <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- </font>
- </family>
- <family lang="und-Yezi">
- <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
- NotoSerifYezidi-VF.ttf
- </font>
- </family>
-</familyset>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index d1aa8e9..8cbc300 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1409,24 +1409,123 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
</font>
<font weight="400" style="normal" index="2" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
</font>
<font weight="400" style="normal" index="3" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
</font>
<font weight="400" style="normal" index="0" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -1443,8 +1542,41 @@
</font>
</family>
<family lang="ko">
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
</font>
<font weight="400" style="normal" index="1" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
deleted file mode 100644
index 8cbc300..0000000
--- a/data/fonts/fonts_cjkvf.xml
+++ /dev/null
@@ -1,1795 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- DEPRECATED: This XML file is no longer a source of the font files installed
- in the system.
-
- For the device vendors: please add your font configurations to the
- platform/frameworks/base/data/font_fallback.xml and also add it to this XML
- file as much as possible for apps that reads this XML file.
-
- For the application developers: please stop reading this XML file and use
- android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
- ASystemFontIterator_open NDK API for getting list of system installed
- font files.
-
- WARNING: Parsing of this file by third-party apps is not supported. The
- file, and the font files it refers to, will be renamed and/or moved out
- from their respective location in the next Android release, and/or the
- format or syntax of the file may change significantly. If you parse this
- file for information about system fonts, do it at your own risk. Your
- application will almost certainly break with the next major Android
- release.
-
- In this file, all fonts without names are added to the default list.
- Fonts are chosen based on a match: full BCP-47 language tag including
- script, then just language, and finally order (the first font containing
- the glyph).
-
- Order of appearance is also the tiebreaker for weight matching. This is
- the reason why the 900 weights of Roboto precede the 700 weights - we
- prefer the former when an 800 weight is requested. Since bold spans
- effectively add 300 to the weight, this ensures that 900 is the bold
- paired with the 500 weight, ensuring adequate contrast.
-
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
--->
-<familyset version="23">
- <!-- first font is default -->
- <family name="sans-serif">
- <font weight="100" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="900" />
- </font>
- <font weight="100" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="900" />
- </font>
- </family>
-
-
- <!-- Note that aliases must come after the fonts they reference. -->
- <alias name="sans-serif-thin" to="sans-serif" weight="100" />
- <alias name="sans-serif-light" to="sans-serif" weight="300" />
- <alias name="sans-serif-medium" to="sans-serif" weight="500" />
- <alias name="sans-serif-black" to="sans-serif" weight="900" />
- <alias name="arial" to="sans-serif" />
- <alias name="helvetica" to="sans-serif" />
- <alias name="tahoma" to="sans-serif" />
- <alias name="verdana" to="sans-serif" />
-
- <family name="sans-serif-condensed">
- <font weight="100" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="900" />
- </font>
- <font weight="100" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="italic">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="1" />
- <axis tag="wdth" stylevalue="75" />
- <axis tag="wght" stylevalue="900" />
- </font>
- </family>
- <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
- <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
-
- <family name="serif">
- <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
- <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
- <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
- <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
- </family>
- <alias name="serif-bold" to="serif" weight="700" />
- <alias name="times" to="serif" />
- <alias name="times new roman" to="serif" />
- <alias name="palatino" to="serif" />
- <alias name="georgia" to="serif" />
- <alias name="baskerville" to="serif" />
- <alias name="goudy" to="serif" />
- <alias name="fantasy" to="serif" />
- <alias name="ITC Stone Serif" to="serif" />
-
- <family name="monospace">
- <font weight="400" style="normal">DroidSansMono.ttf</font>
- </family>
- <alias name="sans-serif-monospace" to="monospace" />
- <alias name="monaco" to="monospace" />
-
- <family name="serif-monospace">
- <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
- </family>
- <alias name="courier" to="serif-monospace" />
- <alias name="courier new" to="serif-monospace" />
-
- <family name="casual">
- <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
- </family>
-
- <family name="cursive">
- <font weight="400" style="normal">DancingScript-Regular.ttf
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="700" style="normal">DancingScript-Regular.ttf
- <axis tag="wght" stylevalue="700" />
- </font>
- </family>
-
- <family name="sans-serif-smallcaps">
- <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
- </family>
-
- <family name="source-sans-pro">
- <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
- <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
- <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
- <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
- <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
- <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
- </family>
- <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
-
- <family name="roboto-flex">
- <font weight="100" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="normal">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="900" />
- </font>
- <font weight="100" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="100" />
- </font>
- <font weight="200" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="200" />
- </font>
- <font weight="300" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="300" />
- </font>
- <font weight="400" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="400" />
- </font>
- <font weight="500" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="500" />
- </font>
- <font weight="600" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="600" />
- </font>
- <font weight="700" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="700" />
- </font>
- <font weight="800" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="800" />
- </font>
- <font weight="900" style="italic">RobotoFlex-Regular.ttf
- <axis tag="slnt" stylevalue="-10" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="900" />
- </font>
- </family>
-
- <!-- fallback fonts -->
- <family lang="und-Arab" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
- NotoNaskhArabic-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
- </family>
- <family lang="und-Arab" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
- NotoNaskhArabicUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
- </family>
- <family lang="und-Ethi">
- <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
- NotoSansEthiopic-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
- NotoSansEthiopic-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
- NotoSansEthiopic-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
- NotoSansEthiopic-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Hebr">
- <font weight="400" style="normal" postScriptName="NotoSansHebrew">
- NotoSansHebrew-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifThai-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
- </family>
- <family lang="und-Thai" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
- NotoSansThaiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
- </family>
- <family lang="und-Armn">
- <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
- NotoSansArmenian-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
- NotoSansArmenian-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
- NotoSansArmenian-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
- NotoSansArmenian-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Geor,und-Geok">
- <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
- NotoSansGeorgian-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
- NotoSansGeorgian-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
- NotoSansGeorgian-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
- NotoSansGeorgian-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Deva" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
- NotoSansDevanagari-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
- NotoSansDevanagari-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
- NotoSansDevanagari-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
- NotoSansDevanagari-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Deva" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
- NotoSansDevanagariUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
- NotoSansDevanagariUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
- NotoSansDevanagariUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
- NotoSansDevanagariUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
-
- <!-- All scripts of India should come after Devanagari, due to shared
- danda characters.
- -->
- <family lang="und-Gujr" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansGujarati">
- NotoSansGujarati-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Gujr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
- NotoSansGujaratiUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
- </family>
- <family lang="und-Guru" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
- NotoSansGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
- NotoSansGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
- NotoSansGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
- NotoSansGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Guru" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
- NotoSansGurmukhiUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
- NotoSansGurmukhiUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
- NotoSansGurmukhiUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
- NotoSansGurmukhiUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Taml" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
- NotoSansTamil-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
- NotoSansTamil-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
- NotoSansTamil-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
- NotoSansTamil-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Taml" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
- NotoSansTamilUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
- NotoSansTamilUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
- NotoSansTamilUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
- NotoSansTamilUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Mlym" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
- NotoSansMalayalam-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
- NotoSansMalayalam-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
- NotoSansMalayalam-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
- NotoSansMalayalam-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Mlym" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
- NotoSansMalayalamUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
- NotoSansMalayalamUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
- NotoSansMalayalamUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
- NotoSansMalayalamUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Beng" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
- NotoSansBengali-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
- NotoSansBengali-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
- NotoSansBengali-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
- NotoSansBengali-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Beng" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
- NotoSansBengaliUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
- NotoSansBengaliUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
- NotoSansBengaliUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
- NotoSansBengaliUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Telu" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
- NotoSansTelugu-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
- NotoSansTelugu-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
- NotoSansTelugu-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
- NotoSansTelugu-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Telu" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
- NotoSansTeluguUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
- NotoSansTeluguUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
- NotoSansTeluguUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
- NotoSansTeluguUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Knda" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
- NotoSansKannada-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
- NotoSansKannada-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
- NotoSansKannada-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
- NotoSansKannada-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Knda" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
- NotoSansKannadaUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
- NotoSansKannadaUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
- NotoSansKannadaUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
- NotoSansKannadaUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Orya" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
- </family>
- <family lang="und-Orya" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
- NotoSansOriyaUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
- </family>
- <family lang="und-Sinh" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
- NotoSansSinhala-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
- NotoSansSinhala-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
- NotoSansSinhala-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
- NotoSansSinhala-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Sinh" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
- NotoSansSinhalaUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
- NotoSansSinhalaUI-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
- NotoSansSinhalaUI-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
- NotoSansSinhalaUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Khmr" variant="elegant">
- <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="26.0"/>
- </font>
- <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="39.0"/>
- </font>
- <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="58.0"/>
- </font>
- <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="90.0"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="108.0"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="128.0"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="151.0"/>
- </font>
- <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="169.0"/>
- </font>
- <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
- NotoSansKhmer-VF.ttf
- <axis tag="wdth" stylevalue="100.0"/>
- <axis tag="wght" stylevalue="190.0"/>
- </font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
- </family>
- <family lang="und-Khmr" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
- NotoSansKhmerUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="elegant">
- <font weight="400" style="normal">NotoSansLao-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">
- NotoSerifLao-Regular.ttf
- </font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
- </family>
- <family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
- </family>
- <family lang="und-Mymr" variant="elegant">
- <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
- </family>
- <family lang="und-Mymr" variant="compact">
- <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
- </family>
- <family lang="und-Thaa">
- <font weight="400" style="normal" postScriptName="NotoSansThaana">
- NotoSansThaana-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
- </family>
- <family lang="und-Cham">
- <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
- </font>
- <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
- </family>
- <family lang="und-Ahom">
- <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
- </family>
- <family lang="und-Adlm">
- <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
- NotoSansAdlam-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
- NotoSansAdlam-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
- NotoSansAdlam-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
- NotoSansAdlam-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Avst">
- <font weight="400" style="normal" postScriptName="NotoSansAvestan">
- NotoSansAvestan-Regular.ttf
- </font>
- </family>
- <family lang="und-Bali">
- <font weight="400" style="normal" postScriptName="NotoSansBalinese">
- NotoSansBalinese-Regular.ttf
- </font>
- </family>
- <family lang="und-Bamu">
- <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
- </font>
- </family>
- <family lang="und-Batk">
- <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
- </font>
- </family>
- <family lang="und-Brah">
- <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
- NotoSansBrahmi-Regular.ttf
- </font>
- </family>
- <family lang="und-Bugi">
- <font weight="400" style="normal" postScriptName="NotoSansBuginese">
- NotoSansBuginese-Regular.ttf
- </font>
- </family>
- <family lang="und-Buhd">
- <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
- </font>
- </family>
- <family lang="und-Cans">
- <font weight="400" style="normal">
- NotoSansCanadianAboriginal-Regular.ttf
- </font>
- </family>
- <family lang="und-Cari">
- <font weight="400" style="normal" postScriptName="NotoSansCarian">
- NotoSansCarian-Regular.ttf
- </font>
- </family>
- <family lang="und-Cakm">
- <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
- </family>
- <family lang="und-Cher">
- <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
- </family>
- <family lang="und-Copt">
- <font weight="400" style="normal" postScriptName="NotoSansCoptic">
- NotoSansCoptic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xsux">
- <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
- NotoSansCuneiform-Regular.ttf
- </font>
- </family>
- <family lang="und-Cprt">
- <font weight="400" style="normal" postScriptName="NotoSansCypriot">
- NotoSansCypriot-Regular.ttf
- </font>
- </family>
- <family lang="und-Dsrt">
- <font weight="400" style="normal" postScriptName="NotoSansDeseret">
- NotoSansDeseret-Regular.ttf
- </font>
- </family>
- <family lang="und-Egyp">
- <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
- NotoSansEgyptianHieroglyphs-Regular.ttf
- </font>
- </family>
- <family lang="und-Elba">
- <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
- </family>
- <family lang="und-Glag">
- <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
- NotoSansGlagolitic-Regular.ttf
- </font>
- </family>
- <family lang="und-Goth">
- <font weight="400" style="normal" postScriptName="NotoSansGothic">
- NotoSansGothic-Regular.ttf
- </font>
- </family>
- <family lang="und-Hano">
- <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
- NotoSansHanunoo-Regular.ttf
- </font>
- </family>
- <family lang="und-Armi">
- <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
- NotoSansImperialAramaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Phli">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
- NotoSansInscriptionalPahlavi-Regular.ttf
- </font>
- </family>
- <family lang="und-Prti">
- <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
- NotoSansInscriptionalParthian-Regular.ttf
- </font>
- </family>
- <family lang="und-Java">
- <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
- </family>
- <family lang="und-Kthi">
- <font weight="400" style="normal" postScriptName="NotoSansKaithi">
- NotoSansKaithi-Regular.ttf
- </font>
- </family>
- <family lang="und-Kali">
- <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
- NotoSansKayahLi-Regular.ttf
- </font>
- </family>
- <family lang="und-Khar">
- <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
- NotoSansKharoshthi-Regular.ttf
- </font>
- </family>
- <family lang="und-Lepc">
- <font weight="400" style="normal" postScriptName="NotoSansLepcha">
- NotoSansLepcha-Regular.ttf
- </font>
- </family>
- <family lang="und-Limb">
- <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
- </font>
- </family>
- <family lang="und-Linb">
- <font weight="400" style="normal" postScriptName="NotoSansLinearB">
- NotoSansLinearB-Regular.ttf
- </font>
- </family>
- <family lang="und-Lisu">
- <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
- </font>
- </family>
- <family lang="und-Lyci">
- <font weight="400" style="normal" postScriptName="NotoSansLycian">
- NotoSansLycian-Regular.ttf
- </font>
- </family>
- <family lang="und-Lydi">
- <font weight="400" style="normal" postScriptName="NotoSansLydian">
- NotoSansLydian-Regular.ttf
- </font>
- </family>
- <family lang="und-Mand">
- <font weight="400" style="normal" postScriptName="NotoSansMandaic">
- NotoSansMandaic-Regular.ttf
- </font>
- </family>
- <family lang="und-Mtei">
- <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
- NotoSansMeeteiMayek-Regular.ttf
- </font>
- </family>
- <family lang="und-Talu">
- <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
- NotoSansNewTaiLue-Regular.ttf
- </font>
- </family>
- <family lang="und-Nkoo">
- <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
- </font>
- </family>
- <family lang="und-Ogam">
- <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
- </font>
- </family>
- <family lang="und-Olck">
- <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
- NotoSansOlChiki-Regular.ttf
- </font>
- </family>
- <family lang="und-Ital">
- <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
- NotoSansOldItalic-Regular.ttf
- </font>
- </family>
- <family lang="und-Xpeo">
- <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
- NotoSansOldPersian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sarb">
- <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
- NotoSansOldSouthArabian-Regular.ttf
- </font>
- </family>
- <family lang="und-Orkh">
- <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
- NotoSansOldTurkic-Regular.ttf
- </font>
- </family>
- <family lang="und-Osge">
- <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
- </family>
- <family lang="und-Osma">
- <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
- NotoSansOsmanya-Regular.ttf
- </font>
- </family>
- <family lang="und-Phnx">
- <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
- NotoSansPhoenician-Regular.ttf
- </font>
- </family>
- <family lang="und-Rjng">
- <font weight="400" style="normal" postScriptName="NotoSansRejang">
- NotoSansRejang-Regular.ttf
- </font>
- </family>
- <family lang="und-Runr">
- <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
- </font>
- </family>
- <family lang="und-Samr">
- <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
- NotoSansSamaritan-Regular.ttf
- </font>
- </family>
- <family lang="und-Saur">
- <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
- NotoSansSaurashtra-Regular.ttf
- </font>
- </family>
- <family lang="und-Shaw">
- <font weight="400" style="normal" postScriptName="NotoSansShavian">
- NotoSansShavian-Regular.ttf
- </font>
- </family>
- <family lang="und-Sund">
- <font weight="400" style="normal" postScriptName="NotoSansSundanese">
- NotoSansSundanese-Regular.ttf
- </font>
- </family>
- <family lang="und-Sylo">
- <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
- NotoSansSylotiNagri-Regular.ttf
- </font>
- </family>
- <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
- <family lang="und-Syre">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
- NotoSansSyriacEstrangela-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrn">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
- NotoSansSyriacEastern-Regular.ttf
- </font>
- </family>
- <family lang="und-Syrj">
- <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
- NotoSansSyriacWestern-Regular.ttf
- </font>
- </family>
- <family lang="und-Tglg">
- <font weight="400" style="normal" postScriptName="NotoSansTagalog">
- NotoSansTagalog-Regular.ttf
- </font>
- </family>
- <family lang="und-Tagb">
- <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
- NotoSansTagbanwa-Regular.ttf
- </font>
- </family>
- <family lang="und-Lana">
- <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
- NotoSansTaiTham-Regular.ttf
- </font>
- </family>
- <family lang="und-Tavt">
- <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
- NotoSansTaiViet-Regular.ttf
- </font>
- </family>
- <family lang="und-Tibt">
- <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
- NotoSerifTibetan-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
- NotoSerifTibetan-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
- NotoSerifTibetan-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
- NotoSerifTibetan-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Tfng">
- <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
- </family>
- <family lang="und-Ugar">
- <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
- NotoSansUgaritic-Regular.ttf
- </font>
- </family>
- <family lang="und-Vaii">
- <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
- </font>
- </family>
- <family>
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
- </family>
- <family lang="zh-Hans">
- <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
- </font>
- <font weight="400" style="normal" index="2" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="zh-Hant,zh-Bopo">
- <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
- </font>
- <font weight="400" style="normal" index="3" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
- </font>
- <font weight="400" style="normal" index="0" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="ja">
- <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
- NotoSerifHentaigana.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
- NotoSerifHentaigana.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="ko">
- <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
- </font>
- <font weight="400" style="normal" index="1" fallbackFor="serif"
- postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
- </font>
- </family>
- <family lang="und-Zsye" ignore="true">
- <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmoji.ttf</font>
- </family>
- <family lang="und-Zsye">
- <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
- </family>
- <family lang="und-Zsym">
- <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
- </family>
- <!--
- Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
- override the East Asian punctuation for Chinese.
- -->
- <family lang="und-Tale">
- <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
- </font>
- </family>
- <family lang="und-Yiii">
- <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
- </family>
- <family lang="und-Mong">
- <font weight="400" style="normal" postScriptName="NotoSansMongolian">
- NotoSansMongolian-Regular.ttf
- </font>
- </family>
- <family lang="und-Phag">
- <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
- NotoSansPhagsPa-Regular.ttf
- </font>
- </family>
- <family lang="und-Hluw">
- <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
- </family>
- <family lang="und-Bass">
- <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
- </family>
- <family lang="und-Bhks">
- <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
- </family>
- <family lang="und-Hatr">
- <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
- </family>
- <family lang="und-Lina">
- <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
- </family>
- <family lang="und-Mani">
- <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
- </family>
- <family lang="und-Marc">
- <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
- </family>
- <family lang="und-Merc">
- <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
- </family>
- <family lang="und-Plrd">
- <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
- </family>
- <family lang="und-Mroo">
- <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
- </family>
- <family lang="und-Mult">
- <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
- </family>
- <family lang="und-Nbat">
- <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
- </family>
- <family lang="und-Newa">
- <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
- </family>
- <family lang="und-Narb">
- <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
- </family>
- <family lang="und-Perm">
- <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
- </family>
- <family lang="und-Hmng">
- <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
- </family>
- <family lang="und-Palm">
- <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
- </family>
- <family lang="und-Pauc">
- <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
- </family>
- <family lang="und-Shrd">
- <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
- </family>
- <family lang="und-Sora">
- <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
- </family>
- <family lang="und-Gong">
- <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
- </family>
- <family lang="und-Rohg">
- <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
- </family>
- <family lang="und-Khoj">
- <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
- </family>
- <family lang="und-Gonm">
- <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
- </family>
- <family lang="und-Wcho">
- <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
- </family>
- <family lang="und-Wara">
- <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
- </family>
- <family lang="und-Gran">
- <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
- </family>
- <family lang="und-Modi">
- <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
- </family>
- <family lang="und-Dogr">
- <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
- </family>
- <family lang="und-Medf">
- <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
- NotoSansMedefaidrin-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
- NotoSansMedefaidrin-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
- NotoSansMedefaidrin-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
- NotoSansMedefaidrin-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Soyo">
- <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
- NotoSansSoyombo-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
- NotoSansSoyombo-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
- NotoSansSoyombo-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
- NotoSansSoyombo-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Takr">
- <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
- NotoSansTakri-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
- NotoSansTakri-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
- NotoSansTakri-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
- NotoSansTakri-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Hmnp">
- <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
- <family lang="und-Yezi">
- <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
- NotoSerifYezidi-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
- NotoSerifYezidi-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
- NotoSerifYezidi-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
- NotoSerifYezidi-VF.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
- </family>
-</familyset>
diff --git a/keystore/java/android/security/OWNERS b/keystore/java/android/security/OWNERS
index 32759b2..4305286 100644
--- a/keystore/java/android/security/OWNERS
+++ b/keystore/java/android/security/OWNERS
@@ -1,2 +1,2 @@
-per-file *.java,*.aidl = eranm@google.com,pgrafov@google.com,rubinxu@google.com
+per-file *.java,*.aidl = drysdale@google.com,jbires@google.com,pgrafov@google.com,rubinxu@google.com
per-file KeyStoreManager.java = mpgroover@google.com
diff --git a/keystore/tests/OWNERS b/keystore/tests/OWNERS
index 86c31f4..0f94ddc 100644
--- a/keystore/tests/OWNERS
+++ b/keystore/tests/OWNERS
@@ -1,4 +1,7 @@
+# Android HW Trust team
+drysdale@google.com
+jbires@google.com
+
# Android Enterprise security team
-eranm@google.com
pgrafov@google.com
rubinxu@google.com
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 6ad2f08..220fc6f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -54,6 +54,7 @@
@NonNull
private final BackupIdler mBackupIdler = new BackupIdler();
private boolean mBackupIdlerScheduled;
+ private boolean mSaveEmbeddingState = false;
private final List<ParcelableTaskContainerData> mParcelableTaskContainerDataList =
new ArrayList<>();
@@ -71,11 +72,32 @@
}
}
+ void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+ if (mSaveEmbeddingState == saveEmbeddingState) {
+ return;
+ }
+
+ Log.i(TAG, "Set save embedding state: " + saveEmbeddingState);
+ mSaveEmbeddingState = saveEmbeddingState;
+ if (!mSaveEmbeddingState) {
+ removeSavedState();
+ return;
+ }
+
+ if (!hasPendingStateToRestore() && !mController.getTaskContainers().isEmpty()) {
+ scheduleBackup();
+ }
+ }
/**
* Schedules a back-up request. It is no-op if there was a request scheduled and not yet
* completed.
*/
void scheduleBackup() {
+ if (!mSaveEmbeddingState) {
+ // TODO(b/289875940): enabled internally for broader testing.
+ return;
+ }
+
if (!mBackupIdlerScheduled) {
mBackupIdlerScheduled = true;
Looper.getMainLooper().getQueue().addIdleHandler(mBackupIdler);
@@ -128,7 +150,6 @@
final List<TaskFragmentInfo> infos = savedState.getParcelableArrayList(
KEY_RESTORE_TASK_FRAGMENTS_INFO, TaskFragmentInfo.class);
for (TaskFragmentInfo info : infos) {
- if (DEBUG) Log.d(TAG, "Retrieved: " + info);
mTaskFragmentInfos.put(info.getFragmentToken(), info);
mPresenter.updateTaskFragmentInfo(info);
}
@@ -140,6 +161,11 @@
if (DEBUG) Log.d(TAG, "Retrieved: " + info);
mTaskFragmentParentInfos.put(info.getTaskId(), info);
}
+
+ if (DEBUG) {
+ Log.d(TAG, "Retrieved task-fragment info: " + infos.size() + ", task info: "
+ + parentInfos.size());
+ }
}
void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
@@ -148,7 +174,6 @@
final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
}
-
removeSavedState();
}
@@ -190,6 +215,9 @@
final ArrayMap<String, EmbeddingRule> embeddingRuleMap = new ArrayMap<>();
for (EmbeddingRule rule : rules) {
embeddingRuleMap.put(rule.getTag(), rule);
+ if (DEBUG) {
+ Log.d(TAG, "Tag: " + rule.getTag() + " rule: " + rule);
+ }
}
boolean restoredAny = false;
@@ -201,7 +229,7 @@
// has unknown tag, unable to restore.
if (DEBUG) {
Log.d(TAG, "Rebuilding TaskContainer abort! Unknown Tag. Task#"
- + parcelableTaskContainerData.mTaskId);
+ + parcelableTaskContainerData.mTaskId + ", tags = " + tags);
}
continue;
}
@@ -217,7 +245,7 @@
final TaskContainer taskContainer = new TaskContainer(parcelableTaskContainerData,
mController, mTaskFragmentInfos);
- if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer);
+ if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer.getTaskId());
mController.addTaskContainer(taskContainer.getTaskId(), taskContainer);
for (ParcelableSplitContainerData splitData :
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3368e2e..60e1a50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2886,6 +2886,18 @@
return getActiveSplitForContainer(container) != null;
}
+
+ @Override
+ public void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+ if (!Flags.aeBackStackRestore()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mPresenter.setAutoSaveEmbeddingState(saveEmbeddingState);
+ }
+ }
+
void scheduleBackup() {
synchronized (mLock) {
mPresenter.scheduleBackup();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b498ee2..9a2f32e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -183,6 +183,10 @@
}
}
+ void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
+ mBackupHelper.setAutoSaveEmbeddingState(saveEmbeddingState);
+ }
+
void scheduleBackup() {
mBackupHelper.scheduleBackup();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index b453f1d..6928409 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -48,8 +48,6 @@
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
-import com.android.window.flags.Flags;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -634,11 +632,7 @@
// pin container.
updateAlwaysOnTopOverlayIfNecessary();
- // TODO(b/289875940): Making backup-restore as an opt-in solution, before the flag goes
- // to next-food.
- if (Flags.aeBackStackRestore()) {
- mSplitController.scheduleBackup();
- }
+ mSplitController.scheduleBackup();
}
private void updateAlwaysOnTopOverlayIfNecessary() {
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 1260796..b2ac640 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
<uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" />
+ <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
<application>
<activity
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
new file mode 100644
index 0000000..2d6df43
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.wm.shell.bubbles
+
+import com.android.internal.logging.testing.UiEventLoggerFake.FakeUiEvent
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+
+/** Subclass of [Subject] to simplify verifying [FakeUiEvent] data */
+class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent) :
+ Subject(metadata, actual) {
+
+ /** Check that [FakeUiEvent] contains the expected data from the [bubble] passed id */
+ fun hasBubbleInfo(bubble: Bubble) {
+ check("uid").that(actual.uid).isEqualTo(bubble.appUid)
+ check("packageName").that(actual.packageName).isEqualTo(bubble.packageName)
+ check("instanceId").that(actual.instanceId).isEqualTo(bubble.instanceId)
+ }
+
+ companion object {
+ @JvmStatic
+ fun assertThat(event: FakeUiEvent): UiEventSubject =
+ Truth.assertAbout(Factory(::UiEventSubject)).that(event)
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
new file mode 100644
index 0000000..af238d0
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.wm.shell.bubbles
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.testing.UiEventLoggerFake.FakeUiEvent
+import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.google.common.truth.ExpectFailure.assertThat
+import com.google.common.truth.ExpectFailure.expectFailure
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [UiEventSubject] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UiEventSubjectTest {
+
+ private val uiEventSubjectFactory =
+ Subject.Factory<UiEventSubject, FakeUiEvent> { metadata, actual ->
+ UiEventSubject(metadata, actual)
+ }
+
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+
+ @Before
+ fun setUp() {
+ uiEventLoggerFake = UiEventLoggerFake()
+ }
+
+ @Test
+ fun test_bubbleLogEvent_hasBubbleInfo() {
+ val bubble =
+ createBubble(
+ appUid = 1,
+ packageName = "test",
+ instanceId = InstanceId.fakeInstanceId(2),
+ )
+ BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+ val uiEvent = uiEventLoggerFake.logs.first()
+
+ // Check that fields match the expected values
+ assertThat(uiEvent.uid).isEqualTo(1)
+ assertThat(uiEvent.packageName).isEqualTo("test")
+ assertThat(uiEvent.instanceId.id).isEqualTo(2)
+
+ // Check that hasBubbleInfo condition passes
+ assertThat(uiEvent).hasBubbleInfo(bubble)
+ }
+
+ @Test
+ fun test_bubbleLogEvent_uidMismatch() {
+ val bubble =
+ createBubble(
+ appUid = 1,
+ packageName = "test",
+ instanceId = InstanceId.fakeInstanceId(2),
+ )
+ BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+ val uiEvent = uiEventLoggerFake.logs.first()
+
+ // Change uid to have a mismatch
+ val otherBubble = bubble.copy(appUid = 99)
+
+ val failure = expectFailure { test ->
+ test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+ }
+ assertThat(failure).factValue("value of").isEqualTo("uiEvent.uid")
+ }
+
+ @Test
+ fun test_bubbleLogEvent_packageNameMismatch() {
+ val bubble =
+ createBubble(
+ appUid = 1,
+ packageName = "test",
+ instanceId = InstanceId.fakeInstanceId(2),
+ )
+ BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+ val uiEvent = uiEventLoggerFake.logs.first()
+
+ // Change package name to have a mismatch
+ val otherBubble = bubble.copy(packageName = "somethingelse")
+
+ val failure = expectFailure { test ->
+ test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+ }
+ assertThat(failure).factValue("value of").isEqualTo("uiEvent.packageName")
+ }
+
+ @Test
+ fun test_bubbleLogEvent_instanceIdMismatch() {
+ val bubble =
+ createBubble(
+ appUid = 1,
+ packageName = "test",
+ instanceId = InstanceId.fakeInstanceId(2),
+ )
+ BubbleLogger(uiEventLoggerFake).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)
+ val uiEvent = uiEventLoggerFake.logs.first()
+
+ // Change instance id to have a mismatch
+ val otherBubble = bubble.copy(instanceId = InstanceId.fakeInstanceId(99))
+
+ val failure = expectFailure { test ->
+ test.about(uiEventSubjectFactory).that(uiEvent).hasBubbleInfo(otherBubble)
+ }
+ assertThat(failure).factValue("value of").isEqualTo("uiEvent.instanceId")
+ }
+
+ private fun createBubble(appUid: Int, packageName: String, instanceId: InstanceId): Bubble {
+ return mock(Bubble::class.java).apply {
+ whenever(getAppUid()).thenReturn(appUid)
+ whenever(getPackageName()).thenReturn(packageName)
+ whenever(getInstanceId()).thenReturn(instanceId)
+ }
+ }
+
+ private fun Bubble.copy(
+ appUid: Int = this.appUid,
+ packageName: String = this.packageName,
+ instanceId: InstanceId = this.instanceId,
+ ): Bubble {
+ return createBubble(appUid, packageName, instanceId)
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index fa9d2ba..08d647d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -40,6 +40,7 @@
import com.android.wm.shell.bubbles.BubbleTaskViewFactory
import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.RegionSamplingProvider
+import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.handles.RegionSamplingHelper
@@ -47,16 +48,14 @@
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.Collections
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import java.util.Collections
-import java.util.concurrent.Executor
/** Tests for [BubbleBarExpandedViewTest] */
@SmallTest
@@ -82,7 +81,7 @@
private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null
private var regionSamplingProvider: TestRegionSamplingProvider? = null
- private val bubbleLogger = spy(BubbleLogger(UiEventLoggerFake()))
+ private val uiEventLoggerFake = UiEventLoggerFake()
@Before
fun setUp() {
@@ -116,7 +115,7 @@
bubbleExpandedView.initialize(
expandedViewManager,
positioner,
- bubbleLogger,
+ BubbleLogger(uiEventLoggerFake),
false /* isOverflow */,
bubbleTaskView,
mainExecutor,
@@ -223,7 +222,10 @@
bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG)
assertThat(dismissMenuItem).isNotNull()
getInstrumentation().runOnMainSync { dismissMenuItem.performClick() }
- verify(bubbleLogger).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 9ca9b73..4569cf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -26,6 +26,7 @@
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
@@ -34,6 +35,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.lang.ref.WeakReference;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
@@ -101,6 +104,40 @@
return mCallback;
}
+ private Runnable mFinishedCallback;
+ private RemoteAnimationTarget[] mApps;
+ private IRemoteAnimationFinishedCallback mRemoteCallback;
+
+ private static class RemoteAnimationFinishedStub extends IRemoteAnimationFinishedCallback.Stub {
+ //the binder callback should not hold strong reference to it to avoid memory leak.
+ private WeakReference<BackAnimationRunner> mRunnerRef;
+
+ private RemoteAnimationFinishedStub(BackAnimationRunner runner) {
+ mRunnerRef = new WeakReference<>(runner);
+ }
+
+ @Override
+ public void onAnimationFinished() {
+ BackAnimationRunner runner = mRunnerRef.get();
+ if (runner == null) {
+ return;
+ }
+ if (runner.shouldMonitorCUJ(runner.mApps)) {
+ InteractionJankMonitor.getInstance().end(runner.mCujType);
+ }
+
+ runner.mFinishedCallback.run();
+ for (int i = runner.mApps.length - 1; i >= 0; --i) {
+ SurfaceControl sc = runner.mApps[i].leash;
+ if (sc != null && sc.isValid()) {
+ sc.release();
+ }
+ }
+ runner.mApps = null;
+ runner.mFinishedCallback = null;
+ }
+ }
+
/**
* Called from {@link IBackAnimationRunner}, it will deliver these
* {@link RemoteAnimationTarget}s to the corresponding runner.
@@ -108,16 +145,9 @@
void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
- final IRemoteAnimationFinishedCallback callback =
- new IRemoteAnimationFinishedCallback.Stub() {
- @Override
- public void onAnimationFinished() {
- if (shouldMonitorCUJ(apps)) {
- interactionJankMonitor.end(mCujType);
- }
- finishedCallback.run();
- }
- };
+ mFinishedCallback = finishedCallback;
+ mApps = apps;
+ if (mRemoteCallback == null) mRemoteCallback = new RemoteAnimationFinishedStub(this);
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
interactionJankMonitor.begin(
@@ -125,7 +155,7 @@
}
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
- nonApps, callback);
+ nonApps, mRemoteCallback);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onAnimationStart", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 03a851b..4c25889 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -24,6 +24,7 @@
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
@@ -266,7 +267,8 @@
AppHandleEducationController appHandleEducationController,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ DesktopModeEventLogger desktopModeEventLogger) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -294,7 +296,8 @@
appHandleEducationController,
windowDecorCaptionHandleRepository,
desktopActivityOrientationHandler,
- focusTransitionObserver);
+ focusTransitionObserver,
+ desktopModeEventLogger);
}
return new CaptionWindowDecorViewModel(
context,
@@ -644,7 +647,10 @@
@ShellMainThread Handler mainHandler,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<RecentTasksController> recentTasksController,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ InputManager inputManager,
+ FocusTransitionObserver focusTransitionObserver,
+ DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, keyguardManager,
@@ -655,7 +661,9 @@
desktopRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
- recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
+ recentTasksController.orElse(null), interactionJankMonitor, mainHandler,
+ inputManager, focusTransitionObserver,
+ desktopModeEventLogger);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
index 320c003..9d4926b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
@@ -71,7 +71,7 @@
/** Whether there is an immersive transition that hasn't completed yet. */
private val inProgress: Boolean
- get() = state != null
+ get() = state != null || pendingExternalExitTransitions.isNotEmpty()
private val rectEvaluator = RectEvaluator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 4350199..df9fc59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -58,9 +58,9 @@
freeformTaskTransitionHandler.startMinimizedModeTransition(wct)
/** Starts close transition and handles or delegates desktop task close animation. */
- override fun startRemoveTransition(wct: WindowContainerTransaction?) {
+ override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
requireNotNull(wct)
- transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+ return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
}
/** Returns null, as it only handles transitions started from Shell. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 8ebe503..255ca6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,11 +16,20 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager.RunningTaskInfo
+import android.util.Size
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.MotionEvent
+import android.view.MotionEvent.TOOL_TYPE_FINGER
+import android.view.MotionEvent.TOOL_TYPE_MOUSE
+import android.view.MotionEvent.TOOL_TYPE_STYLUS
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
import com.android.window.flags.Flags
import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import java.security.SecureRandom
import java.util.Random
@@ -176,7 +185,13 @@
* Logs that a task resize event is starting with [taskSizeUpdate] within a Desktop mode
* session.
*/
- fun logTaskResizingStarted(taskSizeUpdate: TaskSizeUpdate) {
+ fun logTaskResizingStarted(
+ resizeTrigger: ResizeTrigger,
+ motionEvent: MotionEvent?,
+ taskInfo: RunningTaskInfo,
+ displayController: DisplayController? = null,
+ displayLayoutSize: Size? = null,
+ ) {
if (!Flags.enableResizingMetrics()) return
val sessionId = currentSessionId.get()
@@ -188,11 +203,19 @@
return
}
+ val taskSizeUpdate = createTaskSizeUpdate(
+ resizeTrigger,
+ motionEvent,
+ taskInfo,
+ displayController = displayController,
+ displayLayoutSize = displayLayoutSize,
+ )
+
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+ "DesktopModeLogger: Logging task resize is starting, session: %s, taskSizeUpdate: %s",
sessionId,
- taskSizeUpdate.instanceId
+ taskSizeUpdate
)
logTaskSizeUpdated(
FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
@@ -203,7 +226,15 @@
/**
* Logs that a task resize event is ending with [taskSizeUpdate] within a Desktop mode session.
*/
- fun logTaskResizingEnded(taskSizeUpdate: TaskSizeUpdate) {
+ fun logTaskResizingEnded(
+ resizeTrigger: ResizeTrigger,
+ motionEvent: MotionEvent?,
+ taskInfo: RunningTaskInfo,
+ taskHeight: Int? = null,
+ taskWidth: Int? = null,
+ displayController: DisplayController? = null,
+ displayLayoutSize: Size? = null,
+ ) {
if (!Flags.enableResizingMetrics()) return
val sessionId = currentSessionId.get()
@@ -215,18 +246,61 @@
return
}
+ val taskSizeUpdate = createTaskSizeUpdate(
+ resizeTrigger,
+ motionEvent,
+ taskInfo,
+ taskHeight,
+ taskWidth,
+ displayController,
+ displayLayoutSize,
+ )
+
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+ "DesktopModeLogger: Logging task resize is ending, session: %s, taskSizeUpdate: %s",
sessionId,
- taskSizeUpdate.instanceId
+ taskSizeUpdate
)
+
logTaskSizeUpdated(
FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
sessionId, taskSizeUpdate
)
}
+ private fun createTaskSizeUpdate(
+ resizeTrigger: ResizeTrigger,
+ motionEvent: MotionEvent?,
+ taskInfo: RunningTaskInfo,
+ taskHeight: Int? = null,
+ taskWidth: Int? = null,
+ displayController: DisplayController? = null,
+ displayLayoutSize: Size? = null,
+ ): TaskSizeUpdate {
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+ val height = taskHeight ?: taskBounds.height()
+ val width = taskWidth ?: taskBounds.width()
+
+ val displaySize = when {
+ displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
+ displayController != null -> displayController.getDisplayLayout(taskInfo.displayId)
+ ?.let { it.height() * it.width() }
+ else -> null
+ }
+
+ return TaskSizeUpdate(
+ resizeTrigger,
+ getInputMethodFromMotionEvent(motionEvent),
+ taskInfo.taskId,
+ taskInfo.effectiveUid,
+ height,
+ width,
+ displaySize,
+ )
+ }
+
fun logTaskInfoStateInit() {
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD,
@@ -238,7 +312,8 @@
taskHeight = 0,
taskWidth = 0,
taskX = 0,
- taskY = 0)
+ taskY = 0
+ )
)
}
@@ -314,7 +389,7 @@
/* task_width */
taskSizeUpdate.taskWidth,
/* display_area */
- taskSizeUpdate.displayArea
+ taskSizeUpdate.displayArea ?: -1
)
}
@@ -364,9 +439,24 @@
val uid: Int,
val taskHeight: Int,
val taskWidth: Int,
- val displayArea: Int,
+ val displayArea: Int?,
)
+ private fun getInputMethodFromMotionEvent(e: MotionEvent?): InputMethod {
+ if (e == null) return InputMethod.UNKNOWN_INPUT_METHOD
+
+ val toolType = e.getToolType(
+ e.findPointerIndex(e.getPointerId(0))
+ )
+ return when {
+ toolType == TOOL_TYPE_STYLUS -> InputMethod.STYLUS
+ toolType == TOOL_TYPE_MOUSE -> InputMethod.MOUSE
+ toolType == TOOL_TYPE_FINGER && e.source == SOURCE_MOUSE -> InputMethod.TOUCHPAD
+ toolType == TOOL_TYPE_FINGER && e.source == SOURCE_TOUCHSCREEN -> InputMethod.TOUCH
+ else -> InputMethod.UNKNOWN_INPUT_METHOD
+ }
+ }
+
// Default value used when the task was not minimized.
@VisibleForTesting
const val UNSET_MINIMIZE_REASON =
@@ -499,6 +589,10 @@
FrameworkStatsLog
.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
),
+ MAXIMIZE_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER
+ ),
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index eeb7ac8..85a3126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -30,7 +30,6 @@
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopTask
import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -124,7 +123,8 @@
if (!Flags.enableDesktopWindowingPersistence()) return
// TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
mainCoroutineScope.launch {
- val desktop = persistentRepository.readDesktop()
+ val desktop = persistentRepository.readDesktop() ?: return@launch
+
val maxTasks =
DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
?: desktop.zOrderedTasksCount
@@ -132,13 +132,11 @@
desktop.zOrderedTasksList
// Reverse it so we initialize the repo from bottom to top.
.reversed()
- .map { taskId ->
- desktop.tasksByTaskIdMap.getOrDefault(
- taskId,
- DesktopTask.getDefaultInstance()
- )
+ .mapNotNull { taskId ->
+ desktop.tasksByTaskIdMap[taskId]?.takeIf {
+ it.desktopTaskState == DesktopTaskState.VISIBLE
+ }
}
- .filter { task -> task.desktopTaskState == DesktopTaskState.VISIBLE }
.take(maxTasks)
.forEach { task ->
addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
@@ -522,6 +520,7 @@
"${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
)
pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
+ pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 29e302a..69776cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -35,6 +35,9 @@
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Handler
import android.os.IBinder
@@ -42,6 +45,8 @@
import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
@@ -57,6 +62,7 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.hardware.input.Flags.useKeyGestureEventHandler
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -65,6 +71,7 @@
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -78,12 +85,13 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -92,7 +100,6 @@
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
@@ -105,6 +112,7 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
@@ -118,7 +126,7 @@
import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer
-
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
private val context: Context,
@@ -149,11 +157,15 @@
private val recentTasksController: RecentTasksController?,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
+ private val inputManager: InputManager,
+ private val focusTransitionObserver: FocusTransitionObserver,
+ private val desktopModeEventLogger: DesktopModeEventLogger,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
DragAndDropController.DragAndDropListener,
- UserChangeListener {
+ UserChangeListener,
+ KeyGestureEventHandler {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
@@ -226,6 +238,9 @@
}
)
dragAndDropController.addListener(this)
+ if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) {
+ inputManager.registerKeyGestureEventHandler(this)
+ }
}
@VisibleForTesting
@@ -409,7 +424,7 @@
interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo,
+ taskInfo.taskId,
dragToDesktopValueAnimator
)
}
@@ -461,7 +476,12 @@
* @param displayId display id of the window that's being closed
* @param taskId task id of the window that's being closed
*/
- fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) {
+ fun onDesktopWindowClose(
+ wct: WindowContainerTransaction,
+ displayId: Int,
+ taskInfo: RunningTaskInfo,
+ ): ((IBinder) -> Unit)? {
+ val taskId = taskInfo.taskId
if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
removeWallpaperActivity(wct)
}
@@ -472,6 +492,7 @@
taskId
)
)
+ return immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo)
}
fun minimizeTask(taskInfo: RunningTaskInfo) {
@@ -728,7 +749,11 @@
* bounds) and a free floating state (either the last saved bounds if available or the default
* bounds otherwise).
*/
- fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
+ fun toggleDesktopTaskSize(
+ taskInfo: RunningTaskInfo,
+ resizeTrigger: ResizeTrigger,
+ motionEvent: MotionEvent?,
+ ) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
@@ -775,7 +800,10 @@
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
-
+ desktopModeEventLogger.logTaskResizingEnded(
+ resizeTrigger, motionEvent, taskInfo, destinationBounds.height(),
+ destinationBounds.width(), displayController
+ )
toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
}
@@ -865,9 +893,19 @@
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
currentDragBounds: Rect,
- position: SnapPosition
+ position: SnapPosition,
+ resizeTrigger: ResizeTrigger,
+ motionEvent: MotionEvent?,
) {
val destinationBounds = getSnapBounds(taskInfo, position)
+ desktopModeEventLogger.logTaskResizingEnded(
+ resizeTrigger,
+ motionEvent,
+ taskInfo,
+ destinationBounds.height(),
+ destinationBounds.width(),
+ displayController,
+ )
if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) {
// Handle the case where we attempt to snap resize when already snap resized: the task
// position won't need to change but we want to animate the surface going back to the
@@ -896,7 +934,8 @@
position: SnapPosition,
taskSurface: SurfaceControl,
currentDragBounds: Rect,
- dragStartBounds: Rect
+ dragStartBounds: Rect,
+ motionEvent: MotionEvent,
) {
releaseVisualIndicator()
if (!taskInfo.isResizeable && DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue()) {
@@ -913,10 +952,25 @@
isResizable = taskInfo.isResizeable,
)
} else {
+ val resizeTrigger = if (position == SnapPosition.LEFT) {
+ ResizeTrigger.DRAG_LEFT
+ } else {
+ ResizeTrigger.DRAG_RIGHT
+ }
+ desktopModeEventLogger.logTaskResizingStarted(
+ resizeTrigger, motionEvent, taskInfo, displayController
+ )
interactionJankMonitor.begin(
taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
)
- snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
+ snapToHalfScreen(
+ taskInfo,
+ taskSurface,
+ currentDragBounds,
+ position,
+ resizeTrigger,
+ motionEvent,
+ )
}
}
@@ -1581,12 +1635,26 @@
getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
}
+ /** Move the focused desktop task in given `displayId` to next display. */
+ fun moveFocusedTaskToNextDisplay(displayId: Int) {
+ getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) }
+ }
+
private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo ->
taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
}
}
+ // TODO(b/364154795): wait for the completion of moveToNextDisplay transition, otherwise it will
+ // pick a wrong task when a user quickly perform other actions with keyboard shortcuts after
+ // moveToNextDisplay.
+ private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
+ shellTaskOrganizer.getRunningTasks().find { taskInfo ->
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+ focusTransitionObserver.hasGlobalFocus(taskInfo)
+ }
+
/**
* Requests a task be transitioned from desktop to split select. Applies needed windowing
* changes if this transition is enabled.
@@ -1702,6 +1770,7 @@
currentDragBounds: Rect,
validDragArea: Rect,
dragStartBounds: Rect,
+ motionEvent: MotionEvent,
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -1722,12 +1791,22 @@
}
IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
handleSnapResizingTask(
- taskInfo, SnapPosition.LEFT, taskSurface, currentDragBounds, dragStartBounds
+ taskInfo,
+ SnapPosition.LEFT,
+ taskSurface,
+ currentDragBounds,
+ dragStartBounds,
+ motionEvent,
)
}
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
handleSnapResizingTask(
- taskInfo, SnapPosition.RIGHT, taskSurface, currentDragBounds, dragStartBounds
+ taskInfo,
+ SnapPosition.RIGHT,
+ taskSurface,
+ currentDragBounds,
+ dragStartBounds,
+ motionEvent,
)
}
IndicatorType.NO_INDICATOR -> {
@@ -1941,6 +2020,31 @@
taskRepository.dump(pw, innerPrefix)
}
+ override fun handleKeyGestureEvent(
+ event: KeyGestureEvent,
+ focusedToken: IBinder?
+ ): Boolean {
+ if (!isKeyGestureSupported(event.keyGestureType)) return false
+ when (event.keyGestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
+ if (event.keycodes.contains(KeyEvent.KEYCODE_D) &&
+ event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) {
+ logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
+ getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) }
+ return true
+ }
+ return false
+ }
+ else -> return false
+ }
+ }
+
+ override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+ -> enableMoveToNextDisplayShortcut()
+ else -> false
+ }
+
/** The interface for calls from outside the shell, within the host process. */
@ExternalThread
private inner class DesktopModeImpl : DesktopMode {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 34c2f1e..d7d5519 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskInfo: RunningTaskInfo,
- dragToDesktopAnimator: MoveToDesktopAnimator
+ taskId: Int,
+ dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
ProtoLog.v(
@@ -137,26 +137,23 @@
)
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
- // The home launch done above will result in an attempt to move the task to pip if
- // applicable, resulting in a broken state. Prevent that here.
- wct.setDoNotPip(taskInfo.token)
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState =
- if (isSplitTask(taskInfo.taskId)) {
+ if (isSplitTask(taskId)) {
val otherTask =
- getOtherSplitTask(taskInfo.taskId)
+ getOtherSplitTask(taskId)
?: throw IllegalStateException("Expected split task to have a counterpart.")
TransitionState.FromSplit(
- draggedTaskId = taskInfo.taskId,
+ draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
- draggedTaskId = taskInfo.taskId,
+ draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 3f41d7c..2d11e02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -73,15 +73,14 @@
*/
private suspend fun getDesktopRepositoryState(
userId: Int = DEFAULT_USER_ID
- ): DesktopRepositoryState =
+ ): DesktopRepositoryState? =
try {
dataStoreFlow
.first()
- .desktopRepoByUserMap
- .getOrDefault(userId, DesktopRepositoryState.getDefaultInstance())
+ .desktopRepoByUserMap[userId]
} catch (e: Exception) {
Log.e(TAG, "Unable to read from datastore", e)
- DesktopRepositoryState.getDefaultInstance()
+ null
}
/**
@@ -91,13 +90,13 @@
suspend fun readDesktop(
userId: Int = DEFAULT_USER_ID,
desktopId: Int = DEFAULT_DESKTOP_ID,
- ): Desktop =
+ ): Desktop? =
try {
val repository = getDesktopRepositoryState(userId)
- repository.getDesktopOrThrow(desktopId)
+ repository?.getDesktopOrThrow(desktopId)
} catch (e: Exception) {
Log.e(TAG, "Unable to get desktop info from persistent repository", e)
- Desktop.getDefaultInstance()
+ null
}
/** Adds or updates a desktop stored in the datastore */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 6aaf001..58337ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -99,9 +99,11 @@
@Override
- public void startRemoveTransition(WindowContainerTransaction wct) {
+ public IBinder startRemoveTransition(WindowContainerTransaction wct) {
final int type = WindowManager.TRANSIT_CLOSE;
- mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+ final IBinder transition = mTransitions.startTransition(type, wct, this);
+ mPendingTransitionTokens.add(transition);
+ return transition;
}
@Override
@@ -229,8 +231,7 @@
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
SurfaceControl sc = change.getLeash();
finishT.hide(sc);
- Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration
- .getBounds());
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
animator.addUpdateListener(animation -> {
t.setPosition(sc, startBounds.left,
startBounds.top + (animation.getAnimatedFraction() * screenHeight));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index ea68a69..5984d48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -48,6 +48,7 @@
*
* @param wct the {@link WindowContainerTransaction} that closes the task
*
+ * @return the started transition
*/
- void startRemoveTransition(WindowContainerTransaction wct);
+ IBinder startRemoveTransition(WindowContainerTransaction wct);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index b36b1f8..3e6d36c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -110,12 +110,12 @@
void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
@NonNull Executor executor);
- /** Called when device waking up finished. */
- void onFinishedWakingUp();
-
/** Called when device starts going to sleep (screen off). */
void onStartedGoingToSleep();
+ /** Called when device wakes up. */
+ void onStartedWakingUp();
+
/** Called when requested to go to fullscreen from the current active split app. */
void goToFullscreenFromSplit();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index a23b576..6398d31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -471,14 +471,14 @@
mStageCoordinator.onKeyguardStateChanged(visible, occluded);
}
- public void onFinishedWakingUp() {
- mStageCoordinator.onFinishedWakingUp();
- }
-
public void onStartedGoingToSleep() {
mStageCoordinator.onStartedGoingToSleep();
}
+ public void onStartedWakingUp() {
+ mStageCoordinator.onStartedWakingUp();
+ }
+
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
}
@@ -1084,13 +1084,13 @@
}
@Override
- public void onFinishedWakingUp() {
- mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+ public void onStartedGoingToSleep() {
+ mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
}
@Override
- public void onStartedGoingToSleep() {
- mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
+ public void onStartedWakingUp() {
+ mMainExecutor.execute(SplitScreenController.this::onStartedWakingUp);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3e76403..7893267 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1138,14 +1138,10 @@
"onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b",
active, occludingTaskRunning);
setDividerVisibility(!mKeyguardActive, null);
-
- if (active && occludingTaskRunning) {
- dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
}
- void onFinishedWakingUp() {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
+ void onStartedWakingUp() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStartedWakingUp");
if (mBreakOnNextWake) {
dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED);
}
@@ -1908,60 +1904,6 @@
}
}
- /** Callback when split roots have child or haven't under it.
- * NOTICE: This only be called on legacy transition. */
- @Override
- public void onStageHasChildrenChanged(StageTaskListener stageListener) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
- stageListener == mMainStage);
- final boolean hasChildren = stageListener.mHasChildren;
- final boolean isSideStage = stageListener == mSideStage;
- if (!hasChildren && !mIsExiting && isSplitActive()) {
- if (isSideStage && mMainStage.mVisible) {
- // Exit to main stage if side stage no longer has children.
- mSplitLayout.flingDividerToDismiss(
- mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
- EXIT_REASON_APP_FINISHED);
- } else if (!isSideStage && mSideStage.mVisible) {
- // Exit to side stage if main stage no longer has children.
- mSplitLayout.flingDividerToDismiss(
- mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
- EXIT_REASON_APP_FINISHED);
- } else if (!isSplitScreenVisible() && mSplitRequest == null) {
- // Dismiss split screen in the background once any sides of the split become empty.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
- }
- } else if (isSideStage && hasChildren && !isSplitActive()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareEnterSplitScreen(wct);
-
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- if (mIsDropEntering) {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- mIsDropEntering = false;
- mSkipEvictingMainStageChildren = false;
- } else {
- mShowDecorImmediately = true;
- mSplitLayout.flingDividerToCenter(/*finishCallback*/ null);
- }
- });
- }
- if (mMainStage.mHasChildren && mSideStage.mHasChildren) {
- mShouldUpdateRecents = true;
- clearRequestIfPresented();
- updateRecentTasksSplitPair();
-
- if (!mLogger.hasStartedSession() && !mLogger.hasValidEnterSessionId()) {
- mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE);
- }
- mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
- getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLeftRightSplit());
- }
- }
-
@Override
public void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
ActivityManager.RunningTaskInfo taskInfo) {
@@ -2485,6 +2427,10 @@
final int transitType = info.getType();
TransitionInfo.Change pipChange = null;
int closingSplitTaskId = -1;
+ // This array tracks if we are sending stages TO_BACK in this transition.
+ // TODO (b/349828130): Update for n apps
+ boolean[] stagesSentToBack = new boolean[2];
+
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
@@ -2552,23 +2498,31 @@
}
continue;
}
+ final int taskId = taskInfo.taskId;
if (isOpeningType(change.getMode())) {
- if (!stage.containsTask(taskInfo.taskId)) {
+ if (!stage.containsTask(taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
- record.addRecord(stage, true, taskInfo.taskId);
+ + " with " + taskId + " before startAnimation().");
+ record.addRecord(stage, true, taskId);
}
} else if (change.getMode() == TRANSIT_CLOSE) {
- if (stage.containsTask(taskInfo.taskId)) {
- record.addRecord(stage, false, taskInfo.taskId);
+ if (stage.containsTask(taskId)) {
+ record.addRecord(stage, false, taskId);
Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
+ + " with " + taskId + " before startAnimation().");
}
}
if (isClosingType(change.getMode()) &&
- getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) {
- // If either one of the 2 stages is closing we're assuming we'll break split
- closingSplitTaskId = change.getTaskInfo().taskId;
+ getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED) {
+
+ // Record which stages are getting sent to back
+ if (change.getMode() == TRANSIT_TO_BACK) {
+ stagesSentToBack[getStageOfTask(taskId)] = true;
+ }
+
+ // (For PiP transitions) If either one of the 2 stages is closing we're assuming
+ // we'll break split
+ closingSplitTaskId = taskId;
}
}
@@ -2594,6 +2548,21 @@
return true;
}
+ // If keyguard is active, check to see if we have our TO_BACK transitions in order.
+ // This array should either be all false (no split stages sent to back) or all true
+ // (all stages sent to back). In any other case (which can happen with SHOW_ABOVE_LOCKED
+ // apps) we should break split.
+ if (mKeyguardActive) {
+ boolean isFirstStageSentToBack = stagesSentToBack[0];
+ for (boolean b : stagesSentToBack) {
+ // Compare each boolean to the first one. If any are different, break split.
+ if (b != isFirstStageSentToBack) {
+ dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ break;
+ }
+ }
+ }
+
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
|| dismissStages.size() == 1) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6313231..b33f3e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -75,8 +75,6 @@
public interface StageListenerCallbacks {
void onRootTaskAppeared();
- void onStageHasChildrenChanged(StageTaskListener stageTaskListener);
-
void onStageVisibilityChanged(StageTaskListener stageTaskListener);
void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
@@ -207,7 +205,10 @@
mIconProvider);
mHasRootTask = true;
mCallbacks.onRootTaskAppeared();
- sendStatusChanged();
+ if (mVisible != mRootTaskInfo.isVisible) {
+ mVisible = mRootTaskInfo.isVisible;
+ mCallbacks.onStageVisibilityChanged(this);
+ }
mSyncQueue.runInSync(t -> mDimLayer =
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer"));
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
@@ -498,22 +499,6 @@
return true;
}
- private void sendStatusChanged() {
- boolean hasChildren = mChildrenTaskInfo.size() > 0;
- boolean visible = mRootTaskInfo.isVisible;
- if (!mHasRootTask) return;
-
- if (mHasChildren != hasChildren) {
- mHasChildren = hasChildren;
- mCallbacks.onStageHasChildrenChanged(this);
- }
-
- if (mVisible != visible) {
- mVisible = visible;
- mCallbacks.onStageVisibilityChanged(this);
- }
- }
-
@Override
@CallSuper
public void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 509cb85..fde01ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -274,6 +274,7 @@
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
mContext,
+ mTaskInfo,
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9e089b2..a775cbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -32,6 +32,7 @@
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
@@ -56,6 +57,7 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -102,6 +104,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -133,6 +136,7 @@
import kotlin.Pair;
import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
@@ -219,6 +223,7 @@
};
private final TaskPositionerFactory mTaskPositionerFactory;
private final FocusTransitionObserver mFocusTransitionObserver;
+ private final DesktopModeEventLogger mDesktopModeEventLogger;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -246,7 +251,8 @@
AppHandleEducationController appHandleEducationController,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ DesktopModeEventLogger desktopModeEventLogger) {
this(
context,
shellExecutor,
@@ -279,7 +285,8 @@
windowDecorCaptionHandleRepository,
activityOrientationChangeHandler,
new TaskPositionerFactory(),
- focusTransitionObserver);
+ focusTransitionObserver,
+ desktopModeEventLogger);
}
@VisibleForTesting
@@ -315,7 +322,8 @@
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
TaskPositionerFactory taskPositionerFactory,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ DesktopModeEventLogger desktopModeEventLogger) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -376,6 +384,7 @@
};
mTaskPositionerFactory = taskPositionerFactory;
mFocusTransitionObserver = focusTransitionObserver;
+ mDesktopModeEventLogger = desktopModeEventLogger;
shellInit.addInitCallback(this::onInit, this);
}
@@ -545,15 +554,20 @@
>= MANAGE_WINDOWS_MINIMUM_INSTANCES);
}
- private void onMaximizeOrRestore(int taskId, String source) {
+ private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger,
+ MotionEvent motionEvent) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
+ mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
+ decoration.mTaskInfo,
+ mDisplayController, /* displayLayoutSize= */ null);
mInteractionJankMonitor.begin(
decoration.mTaskSurface, mContext, mMainHandler,
Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
- mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger,
+ motionEvent);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -566,7 +580,7 @@
mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo);
}
- private void onSnapResize(int taskId, boolean left) {
+ private void onSnapResize(int taskId, boolean left, MotionEvent motionEvent) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
@@ -577,13 +591,20 @@
Toast.makeText(mContext,
R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
} else {
+ ResizeTrigger resizeTrigger =
+ left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU;
+ mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
+ decoration.mTaskInfo,
+ mDisplayController, /* displayLayoutSize= */ null);
mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
mDesktopTasksController.snapToHalfScreen(
decoration.mTaskInfo,
decoration.mTaskSurface,
decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
- left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+ left ? SnapPosition.LEFT : SnapPosition.RIGHT,
+ resizeTrigger,
+ motionEvent);
}
decoration.closeHandleMenu();
@@ -735,6 +756,7 @@
private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
private int mDragPointerId = -1;
+ private MotionEvent mMotionEvent;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
@@ -767,8 +789,13 @@
SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId);
- mTaskOperations.closeTask(mTaskToken, wct);
+ final Function1<IBinder, Unit> runOnTransitionStart =
+ mDesktopTasksController.onDesktopWindowClose(
+ wct, mDisplayId, decoration.mTaskInfo);
+ final IBinder transition = mTaskOperations.closeTask(mTaskToken, wct);
+ if (transition != null && runOnTransitionStart != null) {
+ runOnTransitionStart.invoke(transition);
+ }
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey(mDisplayId);
@@ -791,7 +818,8 @@
} else {
// Full immersive is disabled or task doesn't request/support it, so just
// toggle between maximize/restore states.
- onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
+ onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button",
+ ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent);
}
} else if (id == R.id.minimize_window) {
mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
@@ -800,6 +828,7 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
+ mMotionEvent = e;
final int id = v.getId();
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
@@ -890,6 +919,7 @@
*/
@Override
public boolean onGenericMotion(View v, MotionEvent ev) {
+ mMotionEvent = ev;
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
@@ -1033,7 +1063,7 @@
taskInfo, decoration.mTaskSurface, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
newTaskBounds, decoration.calculateValidDragArea(),
- new Rect(mOnDragStartInitialBounds));
+ new Rect(mOnDragStartInitialBounds), e);
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -1080,7 +1110,7 @@
// Disallow double-tap to resize when in full immersive.
return false;
}
- onMaximizeOrRestore(mTaskId, "double_tap");
+ onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e);
return true;
}
}
@@ -1477,7 +1507,8 @@
mGenericLinksParser,
mAssistContentRequester,
mMultiInstanceHelper,
- mWindowDecorCaptionHandleRepository);
+ mWindowDecorCaptionHandleRepository,
+ mDesktopModeEventLogger);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final TaskPositioner taskPositioner = mTaskPositionerFactory.create(
@@ -1494,15 +1525,16 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
- onMaximizeOrRestore(taskInfo.taskId, "maximize_menu");
+ onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
+ touchEventListener.mMotionEvent);
return Unit.INSTANCE;
});
windowDecoration.setOnLeftSnapClickListener(() -> {
- onSnapResize(taskInfo.taskId, true /* isLeft */);
+ onSnapResize(taskInfo.taskId, /* isLeft= */ true, touchEventListener.mMotionEvent);
return Unit.INSTANCE;
});
windowDecoration.setOnRightSnapClickListener(() -> {
- onSnapResize(taskInfo.taskId, false /* isLeft */);
+ onSnapResize(taskInfo.taskId, /* isLeft= */ false, touchEventListener.mMotionEvent);
return Unit.INSTANCE;
});
windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6eb20b9..d94f3a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -94,6 +94,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -216,7 +217,8 @@
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
- WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+ DesktopModeEventLogger desktopModeEventLogger) {
this (context, userContext, displayController, splitScreenController, desktopRepository,
taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
@@ -227,7 +229,7 @@
new SurfaceControlViewHostFactory() {},
DefaultMaximizeMenuFactory.INSTANCE,
DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
- windowDecorCaptionHandleRepository);
+ windowDecorCaptionHandleRepository, desktopModeEventLogger);
}
DesktopModeWindowDecoration(
@@ -256,11 +258,12 @@
MaximizeMenuFactory maximizeMenuFactory,
HandleMenuFactory handleMenuFactory,
MultiInstanceHelper multiInstanceHelper,
- WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+ DesktopModeEventLogger desktopModeEventLogger) {
super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlViewHostFactory, desktopModeEventLogger);
mSplitScreenController = splitScreenController;
mHandler = handler;
mBgExecutor = bgExecutor;
@@ -605,6 +608,7 @@
Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener");
mDragResizeListener = new DragResizeInputListener(
mContext,
+ mTaskInfo,
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
@@ -612,7 +616,8 @@
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
mSurfaceControlTransactionSupplier,
- mDisplayController);
+ mDisplayController,
+ mDesktopModeEventLogger);
Trace.endSection();
}
@@ -1700,7 +1705,8 @@
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
- WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+ DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopModeWindowDecoration(
context,
userContext,
@@ -1719,7 +1725,8 @@
genericLinksParser,
assistContentRequester,
multiInstanceHelper,
- windowDecorCaptionHandleRepository);
+ windowDecorCaptionHandleRepository,
+ desktopModeEventLogger);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 4ff394e..4204097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -29,10 +29,12 @@
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen;
import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -59,6 +61,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -83,14 +86,17 @@
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final Context mContext;
+ private final RunningTaskInfo mTaskInfo;
private final SurfaceControl mInputSinkSurface;
private final IBinder mSinkClientToken;
private final InputChannel mSinkInputChannel;
private final DisplayController mDisplayController;
+ private final DesktopModeEventLogger mDesktopModeEventLogger;
private final Region mTouchRegion = new Region();
DragResizeInputListener(
Context context,
+ RunningTaskInfo taskInfo,
Handler handler,
Choreographer choreographer,
int displayId,
@@ -98,12 +104,15 @@
DragPositioningCallback callback,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
- DisplayController displayController) {
+ DisplayController displayController,
+ DesktopModeEventLogger desktopModeEventLogger) {
mContext = context;
+ mTaskInfo = taskInfo;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
mDecorationSurface = decorationSurface;
mDisplayController = displayController;
+ mDesktopModeEventLogger = desktopModeEventLogger;
mClientToken = new Binder();
final InputTransferToken inputTransferToken = new InputTransferToken();
mInputChannel = new InputChannel();
@@ -125,11 +134,12 @@
e.rethrowFromSystemServer();
}
- mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+ mInputEventReceiver = new TaskResizeInputEventReceiver(context, mTaskInfo, mInputChannel,
+ callback,
handler, choreographer, () -> {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
return new Size(layout.width(), layout.height());
- }, this::updateSinkInputChannel);
+ }, this::updateSinkInputChannel, mDesktopModeEventLogger);
mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
mInputSinkSurface = surfaceControlBuilderSupplier.get()
@@ -163,6 +173,22 @@
}
}
+ DragResizeInputListener(
+ Context context,
+ RunningTaskInfo taskInfo,
+ Handler handler,
+ Choreographer choreographer,
+ int displayId,
+ SurfaceControl decorationSurface,
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ DisplayController displayController) {
+ this(context, taskInfo, handler, choreographer, displayId, decorationSurface, callback,
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, displayController,
+ new DesktopModeEventLogger());
+ }
+
/**
* Updates the geometry (the touch region) of this drag resize handler.
*
@@ -274,6 +300,7 @@
private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
DragDetector.MotionEventHandler {
@NonNull private final Context mContext;
+ @NonNull private final RunningTaskInfo mTaskInfo;
private final InputManager mInputManager;
@NonNull private final InputChannel mInputChannel;
@NonNull private final DragPositioningCallback mCallback;
@@ -282,6 +309,7 @@
@NonNull private final DragDetector mDragDetector;
@NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
@NonNull private final Consumer<Region> mTouchRegionConsumer;
+ @NonNull private final DesktopModeEventLogger mDesktopModeEventLogger;
private final Rect mTmpRect = new Rect();
private boolean mConsumeBatchEventScheduled;
private DragResizeWindowGeometry mDragResizeWindowGeometry;
@@ -293,15 +321,24 @@
// resize events. For example, if multiple fingers are touching the screen, then each one
// has a separate pointer id, but we only accept drag input from one.
private int mDragPointerId = -1;
+ // The type of resizing that is currently being done. Used to track the same resize trigger
+ // on start and end of the resizing action.
+ private ResizeTrigger mResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER;
+ // The last MotionEvent on ACTION_DOWN, used to track the input tool type and source for
+ // logging the start and end of the resizing action.
+ private MotionEvent mLastMotionEventOnDown;
private TaskResizeInputEventReceiver(@NonNull Context context,
+ @NonNull RunningTaskInfo taskInfo,
@NonNull InputChannel inputChannel,
@NonNull DragPositioningCallback callback, @NonNull Handler handler,
@NonNull Choreographer choreographer,
@NonNull Supplier<Size> displayLayoutSizeSupplier,
- @NonNull Consumer<Region> touchRegionConsumer) {
+ @NonNull Consumer<Region> touchRegionConsumer,
+ @NonNull DesktopModeEventLogger desktopModeEventLogger) {
super(inputChannel, handler.getLooper());
mContext = context;
+ mTaskInfo = taskInfo;
mInputManager = context.getSystemService(InputManager.class);
mInputChannel = inputChannel;
mCallback = callback;
@@ -322,6 +359,7 @@
ViewConfiguration.get(mContext).getScaledTouchSlop());
mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
mTouchRegionConsumer = touchRegionConsumer;
+ mDesktopModeEventLogger = desktopModeEventLogger;
}
/**
@@ -395,6 +433,7 @@
@Override
public boolean handleMotionEvent(View v, MotionEvent e) {
boolean result = false;
+
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
switch (e.getActionMasked()) {
@@ -416,6 +455,13 @@
"%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
rawX, rawY);
+ mLastMotionEventOnDown = e;
+ mResizeTrigger = (ctrlType == CTRL_TYPE_BOTTOM || ctrlType == CTRL_TYPE_TOP
+ || ctrlType == CTRL_TYPE_RIGHT || ctrlType == CTRL_TYPE_LEFT)
+ ? ResizeTrigger.EDGE : ResizeTrigger.CORNER;
+ mDesktopModeEventLogger.logTaskResizingStarted(mResizeTrigger,
+ e, mTaskInfo, /* displayController= */ null,
+ /* displayLayoutSize= */ mDisplayLayoutSizeSupplier.get());
// Increase the input sink region to cover the whole screen; this is to
// prevent input and focus from going to other tasks during a drag resize.
updateInputSinkRegionForDrag(mDragStartTaskBounds);
@@ -464,6 +510,12 @@
if (taskBounds.equals(mDragStartTaskBounds)) {
mTouchRegionConsumer.accept(mTouchRegion);
}
+
+ mDesktopModeEventLogger.logTaskResizingEnded(mResizeTrigger,
+ mLastMotionEventOnDown, mTaskInfo, taskBounds.height(),
+ taskBounds.width(),
+ /* displayController= */ null,
+ /* displayLayoutSize= */ mDisplayLayoutSizeSupplier.get());
}
mShouldHandleEvents = false;
mDragPointerId = -1;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 33d1c26..844ceb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -181,7 +181,7 @@
}
private boolean isInEdgeResizeBounds(float x, float y) {
- return calculateEdgeResizeCtrlType(x, y) != 0;
+ return calculateEdgeResizeCtrlType(x, y) != CTRL_TYPE_UNDEFINED;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index 61b9393..bc85d2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -76,13 +76,14 @@
closeTask(taskToken, new WindowContainerTransaction());
}
- void closeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
+ IBinder closeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
wct.removeTask(taskToken);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startRemoveTransition(wct);
+ return mTransitionStarter.startRemoveTransition(wct);
} else {
mSyncQueue.queue(wct);
}
+ return null;
}
IBinder minimizeTask(WindowContainerToken taskToken) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6b3b357..34cc098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -58,6 +58,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
@@ -111,6 +112,7 @@
final Context mContext;
final @NonNull Context mUserContext;
final @NonNull DisplayController mDisplayController;
+ final @NonNull DesktopModeEventLogger mDesktopModeEventLogger;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -163,7 +165,7 @@
this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {});
+ new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
}
WindowDecoration(
@@ -177,13 +179,16 @@
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
- SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull DesktopModeEventLogger desktopModeEventLogger
+ ) {
mContext = context;
mUserContext = userContext;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
+ mDesktopModeEventLogger = desktopModeEventLogger;
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index b7ddfd1..4fe66f3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -298,12 +298,18 @@
FlickerConfigEntry(
scenarioId = ScenarioId("MAXIMIZE_APP"),
extractor =
- TaggedScenarioExtractorBuilder()
- .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
- .setTransitionMatcher(
- TaggedCujTransitionMatcher(associatedTransitionRequired = false)
- )
- .build(),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
+ }
+ }
+ }
+ ),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
AppLayerIncreasesInSize(DESKTOP_MODE_APP),
@@ -316,12 +322,18 @@
FlickerConfigEntry(
scenarioId = ScenarioId("MAXIMIZE_APP_NON_RESIZABLE"),
extractor =
- TaggedScenarioExtractorBuilder()
- .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
- .setTransitionMatcher(
- TaggedCujTransitionMatcher(associatedTransitionRequired = false)
- )
- .build(),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
+ }
+ }
+ }
+ ),
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index e6bd05b..f935ac7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -40,6 +40,8 @@
private WindowContainerToken mToken = createMockWCToken();
private int mParentTaskId = INVALID_TASK_ID;
+ private int mUid = INVALID_TASK_ID;
+ private int mTaskId = INVALID_TASK_ID;
private Intent mBaseIntent = new Intent();
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -73,6 +75,18 @@
return this;
}
+ /** Sets the task info's effective UID. */
+ public TestRunningTaskInfoBuilder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ /** Sets the task info's UID. */
+ public TestRunningTaskInfoBuilder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
/**
* Set {@link ActivityManager.RunningTaskInfo#baseIntent} for the task info, by default
* an empty intent is assigned
@@ -132,7 +146,8 @@
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.taskId = sNextTaskId++;
+ info.taskId = (mTaskId == INVALID_TASK_ID) ? sNextTaskId++ : mTaskId;
+ info.effectiveUid = mUid;
info.baseIntent = mBaseIntent;
info.parentTaskId = mParentTaskId;
info.displayId = mDisplayId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
index b137468..ef99b00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
@@ -52,6 +52,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -366,7 +367,7 @@
immersive = false
)
- immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)
+ immersiveHandler.exitImmersiveIfApplicable(wct, task)
assertThat(wct.hasBoundsChange(task.token)).isFalse()
}
@@ -384,12 +385,12 @@
immersive = true
)
- immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+ immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
&& exit.taskId == task.taskId
- }).isFalse()
+ }).isTrue()
}
@Test
@@ -405,7 +406,7 @@
immersive = false
)
- immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+ immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
@@ -565,6 +566,24 @@
).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersive_pendingExit_doesNotExitAgain() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+ immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(Binder())
+
+ immersiveHandler.moveTaskToNonImmersive(task)
+
+ verify(mockTransitions, never()).startTransition(any(), any(), any())
+ }
+
private fun createTransitionInfo(
@TransitionType type: Int = TRANSIT_CHANGE,
@TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 07de0716..81d59d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WindowingMode
+import android.os.Binder
import android.os.Handler
import android.os.IBinder
import android.testing.AndroidTestingRunner
@@ -107,6 +108,8 @@
@Test
fun startRemoveTransition_startsCloseTransition() {
val wct = WindowContainerTransaction()
+ whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
+ .thenReturn(Binder())
mixedHandler.startRemoveTransition(wct)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 0825b6b..2a82e6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,9 +16,12 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions
@@ -27,6 +30,9 @@
import com.android.window.flags.Flags
import com.android.wm.shell.EventLogTags
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
@@ -39,9 +45,13 @@
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Tests for [DesktopModeEventLogger].
@@ -49,6 +59,8 @@
class DesktopModeEventLoggerTest : ShellTestCase() {
private val desktopModeEventLogger = DesktopModeEventLogger()
+ val displayController = mock<DisplayController>()
+ val displayLayout = mock<DisplayLayout>()
@JvmField
@Rule(order = 0)
@@ -60,6 +72,13 @@
@Rule(order = 1)
val setFlagsRule = SetFlagsRule()
+ @Before
+ fun setUp() {
+ doReturn(displayLayout).whenever(displayController).getDisplayLayout(anyInt())
+ doReturn(DISPLAY_WIDTH).whenever(displayLayout).width()
+ doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height()
+ }
+
@Test
fun logSessionEnter_logsEnterReasonWithNewSessionId() {
desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
@@ -467,7 +486,8 @@
@Test
fun logTaskResizingStarted_noOngoingSession_doesNotLog() {
- desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE)
+ desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
+ null, createTaskInfo())
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -478,13 +498,14 @@
fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() {
val sessionId = startDesktopModeSession()
- desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE)
+ desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
+ null, createTaskInfo(), displayController)
verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
/* resize_trigger */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
/* resizing_stage */
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
/* input_method */
@@ -500,7 +521,7 @@
/* task_width */
eq(TASK_SIZE_UPDATE.taskWidth),
/* display_area */
- eq(TASK_SIZE_UPDATE.displayArea),
+ eq(DISPLAY_AREA),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -508,7 +529,8 @@
@Test
fun logTaskResizingEnded_noOngoingSession_doesNotLog() {
- desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE)
+ desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
+ null, createTaskInfo())
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -519,13 +541,14 @@
fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() {
val sessionId = startDesktopModeSession()
- desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE)
+ desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
+ null, createTaskInfo(), displayController = displayController)
verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
/* resize_trigger */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
/* resizing_stage */
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
/* input_method */
@@ -541,7 +564,7 @@
/* task_width */
eq(TASK_SIZE_UPDATE.taskWidth),
/* display_area */
- eq(TASK_SIZE_UPDATE.displayArea),
+ eq(DISPLAY_AREA),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -585,8 +608,14 @@
}
}
+ private fun createTaskInfo(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder().setTaskId(TASK_ID)
+ .setUid(TASK_UID)
+ .setBounds(Rect(TASK_X, TASK_Y, TASK_WIDTH, TASK_HEIGHT))
+ .build()
+ }
+
private companion object {
- private const val sessionId = 1
private const val TASK_ID = 1
private const val TASK_UID = 1
private const val TASK_X = 0
@@ -594,7 +623,9 @@
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
private const val TASK_COUNT = 1
- private const val DISPLAY_AREA = 1000
+ private const val DISPLAY_WIDTH = 500
+ private const val DISPLAY_HEIGHT = 500
+ private const val DISPLAY_AREA = DISPLAY_HEIGHT * DISPLAY_WIDTH
private val TASK_UPDATE = TaskUpdate(
TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 113990e..7c336cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,6 +39,9 @@
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Bundle
import android.os.Handler
@@ -50,6 +53,8 @@
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
+import android.view.KeyEvent
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowInsets
import android.view.WindowManager
@@ -70,14 +75,18 @@
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
+import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -91,6 +100,7 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -112,6 +122,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.transition.Transitions
@@ -205,7 +216,11 @@
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
@Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
@Mock private lateinit var mockHandler: Handler
+ @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock private lateinit var mockInputManager: InputManager
+ @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
+ @Mock lateinit var motionEvent: MotionEvent
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -214,6 +229,7 @@
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
+ private lateinit var keyGestureEventHandler: KeyGestureEventHandler
private val shellExecutor = TestShellExecutor()
@@ -271,6 +287,11 @@
controller.setSplitScreenController(splitScreenController)
controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
+ doAnswer {
+ keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
+ null
+ }.whenever(mockInputManager).registerKeyGestureEventHandler(any())
+
shellInit.init()
val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
@@ -278,6 +299,8 @@
recentsTransitionStateListener = captor.value
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
}
private fun createController(): DesktopTasksController {
@@ -310,6 +333,9 @@
recentTasksController,
mockInteractionJankMonitor,
mockHandler,
+ mockInputManager,
+ mockFocusTransitionObserver,
+ desktopModeEventLogger,
)
}
@@ -338,9 +364,17 @@
val task1 = setUpFreeformTask()
val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(task1)
- verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task1,
+ STABLE_BOUNDS.height(),
+ STABLE_BOUNDS.width(),
+ displayController
+ )
assertThat(argumentCaptor.value).isTrue()
}
@@ -357,9 +391,17 @@
val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(task1)
- verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task1,
+ 0,
+ 0,
+ displayController
+ )
assertThat(argumentCaptor.value).isFalse()
}
@@ -735,7 +777,6 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -754,7 +795,6 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1467,6 +1507,44 @@
}
@Test
+ @EnableFlags(
+ FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
+ FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+ )
+ fun moveToNextDisplay_withKeyGesture() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+ // Setup a focused task on secondary display, which is expected to move to default display
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ task.isFocused = true
+ whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
+ whenever(mockFocusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
+
+ val event = KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
+ .setDisplayId(SECOND_DISPLAY)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
+ .build()
+ val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+
+ assertThat(result).isTrue()
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(1)
+ assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+ assertThat(hierarchyOps[0].isReparent).isTrue()
+ assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+ assertThat(hierarchyOps[0].toTop).isTrue()
+ }
+ }
+
+ @Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()
@@ -1480,8 +1558,9 @@
@Test
fun onDesktopWindowClose_noActiveTasks() {
+ val task = setUpFreeformTask(active = false)
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = 1)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -1490,7 +1569,7 @@
fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
val task = setUpFreeformTask()
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -1502,7 +1581,7 @@
taskRepository.wallpaperActivityToken = wallpaperToken
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
// Adds remove wallpaper operation
wct.assertRemoveAt(index = 0, wallpaperToken)
}
@@ -1515,7 +1594,7 @@
taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -1528,7 +1607,7 @@
taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -1541,7 +1620,7 @@
taskRepository.wallpaperActivityToken = wallpaperToken
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
// Doesn't modify transaction
assertThat(wct.hierarchyOps).isEmpty()
}
@@ -1555,7 +1634,7 @@
taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
// Adds remove wallpaper operation
wct.assertRemoveAt(index = 0, wallpaperToken)
}
@@ -1569,7 +1648,7 @@
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
// Adds remove wallpaper operation
wct.assertRemoveAt(index = 0, wallpaperToken)
}
@@ -1715,8 +1794,6 @@
@Test
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -1733,8 +1810,6 @@
@Test
fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -1760,8 +1835,6 @@
@Test
fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val fullscreenTask = createFullscreenTask()
@@ -1775,8 +1848,6 @@
@Test
fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
@@ -1791,8 +1862,6 @@
@Test
fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
@@ -1808,8 +1877,6 @@
@Test
fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val minimizedTask = setUpFreeformTask()
taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
@@ -1830,7 +1897,6 @@
@Test
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -1847,7 +1913,6 @@
@Test
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1860,8 +1925,6 @@
@Test
fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTask = setUpFreeformTask()
markTaskHidden(freeformTask)
val fullscreenTask = createFullscreenTask()
@@ -1870,16 +1933,12 @@
@Test
fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val fullscreenTask = createFullscreenTask()
assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
}
@Test
fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -1889,8 +1948,6 @@
@Test
fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val newFreeformTask = createFreeformTask()
@@ -1903,8 +1960,6 @@
@Test
fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTask = setUpFreeformTask()
markTaskHidden(freeformTask)
@@ -1919,7 +1974,6 @@
@Test
fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -1934,7 +1988,6 @@
@Test
fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1951,8 +2004,6 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -1968,8 +2019,6 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -1990,8 +2039,6 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
@@ -2003,8 +2050,6 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
@@ -2019,8 +2064,6 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -2035,8 +2078,6 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -2053,7 +2094,6 @@
@Test
fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false)
val freeformTask1 = setUpFreeformTask()
@@ -2067,7 +2107,6 @@
@Test
fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true)
val freeformTask1 = setUpFreeformTask()
@@ -2081,7 +2120,6 @@
@Test
fun handleRequest_freeformTask_keyguardLocked_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2092,8 +2130,6 @@
@Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val task =
TestRunningTaskInfoBuilder()
.setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -2106,21 +2142,17 @@
@Test
fun handleRequest_noTriggerTask_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
}
@Test
fun handleRequest_triggerTaskNotStandard_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
}
@Test
fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
val task =
TestRunningTaskInfoBuilder()
.setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -2802,7 +2834,8 @@
PointF(200f, -200f), /* inputCoordinate */
Rect(100, -100, 500, 1000), /* currentDragBounds */
Rect(0, 50, 2000, 2000), /* validDragArea */
- Rect() /* dragStartBounds */ )
+ Rect() /* dragStartBounds */,
+ motionEvent)
val rectAfterEnd = Rect(100, 50, 500, 1150)
verify(transitions)
.startTransition(
@@ -2837,7 +2870,8 @@
PointF(200f, 300f), /* inputCoordinate */
currentDragBounds, /* currentDragBounds */
Rect(0, 50, 2000, 2000) /* validDragArea */,
- Rect() /* dragStartBounds */)
+ Rect() /* dragStartBounds */,
+ motionEvent)
verify(transitions)
@@ -3084,10 +3118,19 @@
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ STABLE_BOUNDS.height(),
+ STABLE_BOUNDS.width(),
+ displayController
+ )
}
@Test
@@ -3106,15 +3149,22 @@
STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
)
- controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+ controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, motionEvent)
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ motionEvent,
+ task,
+ expectedBounds.height(),
+ expectedBounds.width(),
+ displayController
+ )
}
@Test
fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
// Set up task to already be in snapped-left bounds
val bounds = Rect(
STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
@@ -3129,7 +3179,7 @@
// Attempt to snap left again
val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
- controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+ controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, motionEvent)
// Assert that task is NOT updated via WCT
verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
@@ -3142,6 +3192,14 @@
eq(bounds),
eq(true)
)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ motionEvent,
+ task,
+ bounds.height(),
+ bounds.width(),
+ displayController
+ )
}
@Test
@@ -3152,12 +3210,22 @@
}
val preDragBounds = Rect(100, 100, 400, 500)
val currentDragBounds = Rect(0, 100, 300, 500)
+ val expectedBounds =
+ Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom)
controller.handleSnapResizingTask(
- task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+ task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent
+ )
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(
- Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom))
+ expectedBounds
+ )
+ verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
+ ResizeTrigger.DRAG_LEFT,
+ motionEvent,
+ task,
+ displayController
+ )
}
@Test
@@ -3170,7 +3238,7 @@
val currentDragBounds = Rect(0, 100, 300, 500)
controller.handleSnapResizingTask(
- task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+ task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent)
verify(mReturnToDragStartAnimator).start(
eq(task.taskId),
eq(mockSurface),
@@ -3178,6 +3246,13 @@
eq(preDragBounds),
eq(false)
)
+ verify(desktopModeEventLogger, never()).logTaskResizingStarted(
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
}
@Test
@@ -3196,10 +3271,19 @@
// Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ expectedBounds.height(),
+ expectedBounds.width(),
+ displayController
+ )
}
@Test
@@ -3207,8 +3291,12 @@
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
+ verify(desktopModeEventLogger, never()).logTaskResizingEnded(
+ any(), any(), any(), any(),
+ any(), any(), any()
+ )
}
@Test
@@ -3217,15 +3305,23 @@
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
// Maximize
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
// Restore
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ boundsBeforeMaximize.height(),
+ boundsBeforeMaximize.width(),
+ displayController
+ )
}
@Test
@@ -3236,16 +3332,24 @@
}
// Maximize
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
// Restore
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ boundsBeforeMaximize.height(),
+ boundsBeforeMaximize.width(),
+ displayController
+ )
}
@Test
@@ -3256,16 +3360,24 @@
}
// Maximize
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
// Restore
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ boundsBeforeMaximize.height(),
+ boundsBeforeMaximize.width(),
+ displayController
+ )
}
@Test
@@ -3274,14 +3386,22 @@
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
// Maximize
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
// Restore
- controller.toggleDesktopTaskSize(task)
+ controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
// Assert last bounds before maximize removed after use
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ motionEvent,
+ task,
+ boundsBeforeMaximize.height(),
+ boundsBeforeMaximize.width(),
+ displayController
+ )
}
@@ -3680,14 +3800,11 @@
handlerClass: Class<out TransitionHandler>? = null
): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- if (handlerClass == null) {
- verify(transitions).startTransition(eq(type), arg.capture(), isNull())
- } else {
- verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
- }
+
+ if (handlerClass == null) {
+ verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
}
return arg.value
}
@@ -3697,43 +3814,27 @@
): WindowContainerTransaction {
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
.startTransition(capture(arg), eq(currentBounds))
- } else {
- verify(shellTaskOrganizer).applyTransaction(capture(arg))
- }
return arg.value
}
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- } else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
- }
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
return arg.value
}
private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- } else {
- verify(shellTaskOrganizer).applyTransaction(capture(arg))
- }
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
return arg.value
}
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- } else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
- }
+ verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
return arg.value
}
@@ -3741,27 +3842,15 @@
wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
private fun verifyWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions, never()).startTransition(anyInt(), any(), isNull())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
- }
+ verify(transitions, never()).startTransition(anyInt(), any(), isNull())
}
private fun verifyExitDesktopWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
- }
+ verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
}
private fun verifyEnterDesktopWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
- }
+ verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
}
private fun createTransition(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 0bd3e08..79e16fe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -607,7 +607,7 @@
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task, dragAnimator)
+ handler.startDragToDesktopTransition(task.taskId, dragAnimator)
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
index 9b9703f..8495580 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -115,8 +115,8 @@
freeformTasksInZOrder = freeformTasksInZOrder)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
- assertThat(actualDesktop.tasksByTaskIdMap).hasSize(2)
- assertThat(actualDesktop.getZOrderedTasks(0)).isEqualTo(2)
+ assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2)
+ assertThat(actualDesktop?.getZOrderedTasks(0)).isEqualTo(2)
}
}
@@ -138,7 +138,7 @@
freeformTasksInZOrder = freeformTasksInZOrder)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
- assertThat(actualDesktop.tasksByTaskIdMap[task.taskId]?.desktopTaskState)
+ assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState)
.isEqualTo(DesktopTaskState.MINIMIZED)
}
}
@@ -161,8 +161,8 @@
freeformTasksInZOrder = freeformTasksInZOrder)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
- assertThat(actualDesktop.tasksByTaskIdMap).isEmpty()
- assertThat(actualDesktop.zOrderedTasksList).isEmpty()
+ assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty()
+ assertThat(actualDesktop?.zOrderedTasksList).isEmpty()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a6e33e5..a252a9d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -320,7 +320,7 @@
assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN);
- mStageCoordinator.onFinishedWakingUp();
+ mStageCoordinator.onStartedWakingUp();
verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 189684d..7144a1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -114,7 +114,6 @@
public void testRootTaskAppeared() {
assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
verify(mCallbacks).onRootTaskAppeared();
- verify(mCallbacks, never()).onStageHasChildrenChanged(mStageTaskListener);
verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 175fbd2..1839b8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -87,6 +87,8 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -194,7 +196,11 @@
@Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController
@Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
@Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
+ @Mock private lateinit var motionEvent: MotionEvent
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
private lateinit var spyContext: TestableContext
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -224,6 +230,7 @@
shellInit = ShellInit(mockShellExecutor)
windowDecorByTaskIdSpy.clear()
spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
+ desktopModeEventLogger = mock<DesktopModeEventLogger>()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
mockShellExecutor,
@@ -256,7 +263,8 @@
mockCaptionHandleRepository,
Optional.of(mockActivityOrientationChangeHandler),
mockTaskPositionerFactory,
- mockFocusTransitionObserver
+ mockFocusTransitionObserver,
+ desktopModeEventLogger
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -299,6 +307,10 @@
argumentCaptor<DesktopModeKeyguardChangeListener>()
verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
}
@After
@@ -612,7 +624,11 @@
maxOrRestoreListenerCaptor.value.invoke()
- verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
+ verify(mockDesktopTasksController).toggleDesktopTaskSize(
+ decor.mTaskInfo,
+ ResizeTrigger.MAXIMIZE_MENU,
+ null
+ )
}
@Test
@@ -647,7 +663,9 @@
eq(decor.mTaskInfo),
taskSurfaceCaptor.capture(),
eq(currentBounds),
- eq(SnapPosition.LEFT)
+ eq(SnapPosition.LEFT),
+ eq(ResizeTrigger.SNAP_LEFT_MENU),
+ eq(null)
)
assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface)
}
@@ -685,7 +703,9 @@
eq(decor.mTaskInfo),
taskSurfaceCaptor.capture(),
eq(currentBounds),
- eq(SnapPosition.LEFT)
+ eq(SnapPosition.LEFT),
+ eq(ResizeTrigger.SNAP_LEFT_MENU),
+ eq(null)
)
assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@@ -704,7 +724,9 @@
onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT))
+ .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+ eq(ResizeTrigger.MAXIMIZE_BUTTON),
+ eq(null))
verify(mockToast).show()
}
@@ -725,7 +747,9 @@
eq(decor.mTaskInfo),
taskSurfaceCaptor.capture(),
eq(currentBounds),
- eq(SnapPosition.RIGHT)
+ eq(SnapPosition.RIGHT),
+ eq(ResizeTrigger.SNAP_RIGHT_MENU),
+ eq(null)
)
assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@@ -763,7 +787,9 @@
eq(decor.mTaskInfo),
taskSurfaceCaptor.capture(),
eq(currentBounds),
- eq(SnapPosition.RIGHT)
+ eq(SnapPosition.RIGHT),
+ eq(ResizeTrigger.SNAP_RIGHT_MENU),
+ eq(null)
)
assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
}
@@ -782,7 +808,9 @@
onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT))
+ .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+ eq(ResizeTrigger.MAXIMIZE_BUTTON),
+ eq(null))
verify(mockToast).show()
}
@@ -1247,7 +1275,7 @@
onClickListenerCaptor.value.onClick(view)
verify(mockDesktopTasksController)
- .toggleDesktopTaskSize(decor.mTaskInfo)
+ .toggleDesktopTaskSize(decor.mTaskInfo, ResizeTrigger.MAXIMIZE_BUTTON, null)
}
private fun createOpenTaskDecoration(
@@ -1337,7 +1365,7 @@
whenever(
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any())
+ any(), any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
@@ -1378,5 +1406,6 @@
private const val TAG = "DesktopModeWindowDecorViewModelTests"
private val STABLE_INSETS = Rect(0, 100, 0, 0)
private val INITIAL_BOUNDS = Rect(0, 0, 100, 100)
+ private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3208872..0afb6c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,6 +106,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -210,6 +211,8 @@
private MultiInstanceHelper mMockMultiInstanceHelper;
@Mock
private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
+ @Mock
+ private DesktopModeEventLogger mDesktopModeEventLogger;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -1400,7 +1403,7 @@
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
maximizeMenuFactory, mMockHandleMenuFactory,
- mMockMultiInstanceHelper, mMockCaptionHandleRepository);
+ mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fb17ae9..cb7fade 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -83,6 +83,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
@@ -138,6 +139,8 @@
private SurfaceSyncGroup mMockSurfaceSyncGroup;
@Mock
private SurfaceControl mMockTaskSurface;
+ @Mock
+ private DesktopModeEventLogger mDesktopModeEventLogger;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -1014,7 +1017,7 @@
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
() -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
- mMockSurfaceControlViewHostFactory);
+ mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1054,11 +1057,12 @@
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
- SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ DesktopModeEventLogger desktopModeEventLogger) {
super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlViewHostFactory, desktopModeEventLogger);
}
@Override
diff --git a/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp b/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp
new file mode 100644
index 0000000..5905b32
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <math.h>
+
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "include/gpu/GpuTypes.h" // from Skia
+#include "tests/common/BitmapAllocationTestUtils.h"
+#include "utils/Color.h"
+
+class WindowBlurKawase;
+
+static TestScene::Registrar _WindowBlurKawase(TestScene::Info{
+ "windowblurkawase", "Draws window Kawase blur",
+ TestScene::simpleCreateScene<WindowBlurKawase>});
+
+/**
+ * Simulates the multi-pass Kawase blur algorithm in
+ * frameworks/native/libs/renderengine/skia/filters/WindowBlurKawaseFilter.cpp
+ */
+class WindowBlurKawase : public TestScene {
+private:
+ // Keep in sync with
+ // frameworks/native/libs/renderengine/skia/filters/KawaseBlurFilter.h
+ static constexpr uint32_t kMaxPasses = 4;
+ // Keep in sync with frameworks/native/libs/renderengine/skia/filters/BlurFilter.h
+ static constexpr float kInputScale = 0.25f;
+
+ static constexpr uint32_t kLoopLength = 500;
+ static constexpr uint32_t kMaxBlurRadius = 300;
+ sk_sp<SkRuntimeEffect> mBlurEffect;
+
+ sp<RenderNode> card;
+ sp<RenderNode> contentNode;
+
+public:
+ explicit WindowBlurKawase() {
+ SkString blurString(
+ "uniform shader child;"
+ "uniform float in_blurOffset;"
+
+ "half4 main(float2 xy) {"
+ "half4 c = child.eval(xy);"
+ "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
+ "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
+ "return half4(c.rgb * 0.2, 1.0);"
+ "}");
+
+ auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
+ if (!blurEffect) {
+ LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
+ }
+ mBlurEffect = std::move(blurEffect);
+ }
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ contentNode = TestUtils::createNode(
+ 0, 0, width, height, [width, height](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+ Paint paint;
+ paint.setColor(Color::Red_500);
+ canvas.drawRect(0, 0, width / 2, height / 2, paint);
+ paint.setColor(Color::Blue_500);
+ canvas.drawRect(width / 2, height / 2, width, height, paint);
+ });
+
+ card = TestUtils::createNode(
+ 0, 0, width, height,
+ [this](RenderProperties& props, Canvas& canvas) { blurFrame(canvas, 0); });
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % kLoopLength;
+ float blurRadius =
+ (sin((float)curFrame / kLoopLength * M_PI * 2) + 1) * 0.5 * kMaxBlurRadius;
+ TestUtils::recordNode(
+ *card, [this, blurRadius](Canvas& canvas) { blurFrame(canvas, blurRadius); });
+ }
+
+ void blurFrame(Canvas& canvas, float blurRadius) {
+ if (blurRadius == 0) {
+ canvas.drawRenderNode(contentNode.get());
+ return;
+ }
+
+ int width = canvas.width();
+ int height = canvas.height();
+ float tmpRadius = (float)blurRadius / 2.0f;
+ uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+ float radiusByPasses = tmpRadius / (float)numberOfPasses;
+
+ SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+
+ sp<RenderNode> node = contentNode;
+ for (int i = 0; i < numberOfPasses; i++) {
+ blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale * (i + 1);
+ sk_sp<SkImageFilter> blurFilter =
+ SkImageFilters::RuntimeShader(blurBuilder, radiusByPasses, "child", nullptr);
+ // Also downsample the image in the first pass.
+ float canvasScale = i == 0 ? kInputScale : 1;
+
+ // Apply the blur effect as an image filter.
+ node = TestUtils::createNode(
+ 0, 0, width * kInputScale, height * kInputScale,
+ [node, blurFilter, canvasScale](RenderProperties& props, Canvas& canvas) {
+ props.mutateLayerProperties().setImageFilter(blurFilter.get());
+ canvas.scale(canvasScale, canvasScale);
+ canvas.drawRenderNode(node.get());
+ });
+ }
+
+ // Finally upsample the image to its original size.
+ canvas.scale(1 / kInputScale, 1 / kInputScale);
+ canvas.drawRenderNode(node.get());
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp b/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp
new file mode 100644
index 0000000..36e6d8f
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/WindowBlurSkia.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <math.h>
+
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "include/gpu/GpuTypes.h" // from Skia
+#include "tests/common/BitmapAllocationTestUtils.h"
+#include "utils/Color.h"
+
+class WindowBlurSkia;
+
+static TestScene::Registrar _WindowBlurSkia(TestScene::Info{
+ "windowblurskia", "Draws window Skia blur", TestScene::simpleCreateScene<WindowBlurSkia>});
+
+/**
+ * Simulates the Skia window blur in
+ * frameworks/native/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+ */
+class WindowBlurSkia : public TestScene {
+private:
+ // Keep in sync with frameworks/native/libs/renderengine/skia/filters/BlurFilter.h
+ static constexpr float kInputScale = 0.25f;
+
+ static constexpr uint32_t kLoopLength = 500;
+ static constexpr uint32_t kMaxBlurRadius = 300;
+
+ sp<RenderNode> card;
+ sp<RenderNode> contentNode;
+
+public:
+ void createContent(int width, int height, Canvas& canvas) override {
+ contentNode = TestUtils::createNode(
+ 0, 0, width, height, [width, height](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+ Paint paint;
+ paint.setColor(Color::Red_500);
+ canvas.drawRect(0, 0, width / 2, height / 2, paint);
+ paint.setColor(Color::Blue_500);
+ canvas.drawRect(width / 2, height / 2, width, height, paint);
+ });
+
+ card = TestUtils::createNode(
+ 0, 0, width, height,
+ [this](RenderProperties& props, Canvas& canvas) { blurFrame(canvas, 0); });
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % kLoopLength;
+ float blurRadius =
+ (sin((float)curFrame / kLoopLength * M_PI * 2) + 1) * 0.5 * kMaxBlurRadius;
+ TestUtils::recordNode(
+ *card, [this, blurRadius](Canvas& canvas) { blurFrame(canvas, blurRadius); });
+ }
+
+ void blurFrame(Canvas& canvas, float blurRadius) {
+ if (blurRadius == 0) {
+ canvas.drawRenderNode(contentNode.get());
+ return;
+ }
+
+ int width = canvas.width();
+ int height = canvas.height();
+
+ // Downsample and blur the image with the Skia blur filter.
+ sp<RenderNode> node = contentNode;
+ sk_sp<SkImageFilter> blurFilter =
+ SkImageFilters::Blur(blurRadius, blurRadius, SkTileMode::kClamp, nullptr, nullptr);
+ node = TestUtils::createNode(
+ 0, 0, width * kInputScale, height * kInputScale,
+ [node, blurFilter](RenderProperties& props, Canvas& canvas) {
+ props.mutateLayerProperties().setImageFilter(blurFilter.get());
+ canvas.scale(kInputScale, kInputScale);
+ canvas.drawRenderNode(node.get());
+ });
+
+ // Upsample the image to its original size.
+ canvas.scale(1 / kInputScale, 1 / kInputScale);
+ canvas.drawRenderNode(node.get());
+ }
+};
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 23097f6..c7a7ed2 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -17,7 +17,6 @@
#include "Color.h"
#include <Properties.h>
-#include <aidl/android/hardware/graphics/common/Dataspace.h>
#include <android/hardware_buffer.h>
#include <android/native_window.h>
#include <ui/ColorSpace.h>
@@ -222,8 +221,7 @@
if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) {
return HAL_DATASPACE_BT2020;
} else if (nearlyEqual(fn, SkNamedTransferFn::kSRGB)) {
- return static_cast<android_dataspace>(
- ::aidl::android::hardware::graphics::common::Dataspace::DISPLAY_BT2020);
+ return static_cast<android_dataspace>(HAL_DATASPACE_DISPLAY_BT2020);
}
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index afa0a32..65e83b9 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -323,10 +323,15 @@
int status;
try {
- status = mSoundTriggerSession.startRecognition(mSoundModel,
- mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
- allowMultipleTriggers, null, null, audioCapabilities),
- runInBatterySaver);
+ status = mSoundTriggerSession.startRecognition(
+ mSoundModel,
+ mRecognitionCallback,
+ new RecognitionConfig.Builder()
+ .setCaptureRequested(captureTriggerAudio)
+ .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setAudioCapabilities(audioCapabilities)
+ .build(),
+ runInBatterySaver);
} catch (RemoteException e) {
return false;
}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 2e3ee32..e3f8fbb 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -20,6 +20,8 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="@dimen/settingslib_switchbar_margin"
android:paddingBottom="@dimen/settingslib_switchbar_margin"
android:orientation="vertical">
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index 3e0e184..255b2c9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -20,6 +20,8 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="@dimen/settingslib_switchbar_margin"
android:paddingBottom="@dimen/settingslib_switchbar_margin"
android:orientation="vertical">
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml
new file mode 100644
index 0000000..94c6924
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:importantForAccessibility="no">
+
+ <com.android.settingslib.widget.MainSwitchBar
+ android:id="@+id/settingslib_main_switch_bar"
+ android:visibility="gone"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent" />
+
+</FrameLayout>
+
+
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 7c0eaea..bf34db9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -18,7 +18,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="match_parent">
+ android:layout_width="match_parent"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/switch_text"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
index fa908a4..bef6e35 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
@@ -18,10 +18,6 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:importantForAccessibility="no">
<com.android.settingslib.widget.MainSwitchBar
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index 3394874..83858d9 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -81,7 +81,11 @@
}
private void init(Context context, AttributeSet attrs) {
- setLayoutResource(R.layout.settingslib_main_switch_layout);
+ boolean isExpressive = SettingsThemeHelper.isExpressiveTheme(context);
+ int resId = isExpressive
+ ? R.layout.settingslib_expressive_main_switch_layout
+ : R.layout.settingslib_main_switch_layout;
+ setLayoutResource(resId);
mSwitchChangeListeners.add(this);
if (attrs != null) {
final TypedArray a = context.obtainStyledAttributes(attrs,
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index ad996c7..b64f5dc 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -38,3 +38,11 @@
@StringRes override val title: Int = 0,
@StringRes override val summary: Int = 0,
) : TwoStatePreference
+
+/** A preference that provides a two-state toggleable option that can be used as a main switch. */
+open class MainSwitchPreference
+@JvmOverloads
+constructor(
+ override val key: String,
+ @StringRes override val title: Int = 0,
+) : TwoStatePreference
\ No newline at end of file
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index bff95ce..fb06be9 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -32,6 +32,7 @@
static_libs: [
"SettingsLibDataStore",
"SettingsLibMetadata",
+ "SettingsLibMainSwitchPreference",
"androidx.annotation_annotation",
"androidx.preference_preference",
"guava",
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 4c2e1ba..43f2cb6 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.preference
+import com.android.settingslib.metadata.MainSwitchPreference
import com.android.settingslib.metadata.PreferenceGroup
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.SwitchPreference
@@ -36,6 +37,7 @@
is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
is PreferenceGroup -> PreferenceGroupBinding.INSTANCE
is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
+ is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
else -> DefaultPreferenceBinding
}
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index ede970e..d40a6f6 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -21,11 +21,13 @@
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
+import androidx.preference.TwoStatePreference
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.widget.MainSwitchPreference
/** Binding of preference group associated with [PreferenceCategory]. */
interface PreferenceScreenBinding : PreferenceBinding {
@@ -64,23 +66,37 @@
}
}
-/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
-interface SwitchPreferenceBinding : PreferenceBinding {
-
- override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
+/** A boolean value type preference associated with the abstract [TwoStatePreference]. */
+interface TwoStatePreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
(metadata as? PersistentPreference<*>)
?.storage(preference.context)
?.getValue(metadata.key, Boolean::class.javaObjectType)
- ?.let { (preference as SwitchPreferenceCompat).isChecked = it }
+ ?.let { (preference as TwoStatePreference).isChecked = it }
}
+}
+
+/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
+interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
+
+ override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
companion object {
@JvmStatic val INSTANCE = object : SwitchPreferenceBinding {}
}
}
+/** A boolean value type preference associated with [MainSwitchPreference]. */
+interface MainSwitchPreferenceBinding : TwoStatePreferenceBinding {
+
+ override fun createWidget(context: Context): Preference = MainSwitchPreference(context)
+
+ companion object {
+ @JvmStatic val INSTANCE = object : MainSwitchPreferenceBinding {}
+ }
+}
+
/** Default [PreferenceBinding] for [Preference]. */
object DefaultPreferenceBinding : PreferenceBinding
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 4f315a2..63661f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -74,7 +74,23 @@
new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
- applyDefaultSelectedTypeToAllPresets();
+ // Activate the last hot plugged valid input device, to match the output device
+ // behavior.
+ @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
+ for (AudioDeviceInfo info : addedDevices) {
+ if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
+ deviceTypeToActivate = info.getType();
+ }
+ }
+
+ // Only activate if we find a different valid input device. e.g. if none of the
+ // addedDevices is supported input device, we don't need to activate anything.
+ if (mSelectedInputDeviceType != deviceTypeToActivate) {
+ mSelectedInputDeviceType = deviceTypeToActivate;
+ AudioDeviceAttributes deviceAttributes =
+ createInputDeviceAttributes(mSelectedInputDeviceType);
+ setPreferredDeviceForAllPresets(deviceAttributes);
+ }
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index d3c345d..f5e6caf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -25,6 +25,7 @@
import android.media.IVolumeController
import android.provider.Settings
import android.util.Log
+import android.view.KeyEvent
import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.volume.data.model.VolumeControllerEvent
@@ -104,6 +105,8 @@
@AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
suspend fun notifyVolumeControllerVisible(isVisible: Boolean)
+
+ fun dispatchMediaKeyEvent(event: KeyEvent)
}
class AudioRepositoryImpl(
@@ -265,6 +268,10 @@
}
}
+ override fun dispatchMediaKeyEvent(event: KeyEvent) {
+ audioManager.dispatchMediaKeyEvent(event)
+ }
+
private fun getMinVolume(stream: AudioStream): Int =
try {
audioManager.getStreamMinVolume(stream.value)
@@ -320,15 +327,9 @@
mutableEvents.tryEmit(VolumeControllerEvent.SetA11yMode(mode))
}
- override fun displayCsdWarning(
- csdWarning: Int,
- displayDurationMs: Int,
- ) {
+ override fun displayCsdWarning(csdWarning: Int, displayDurationMs: Int) {
mutableEvents.tryEmit(
- VolumeControllerEvent.DisplayCsdWarning(
- csdWarning,
- displayDurationMs,
- )
+ VolumeControllerEvent.DisplayCsdWarning(csdWarning, displayDurationMs)
)
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 782cee2..d808a25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -138,6 +139,18 @@
/* address= */ "");
}
+ private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ /* address= */ "");
+ }
+
+ private AudioDeviceAttributes getHdmiDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
+ }
+
private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
final List<AudioDeviceAttributes> audioDeviceAttributesList =
new ArrayList<AudioDeviceAttributes>();
@@ -303,21 +316,47 @@
}
@Test
- public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+ public void onAudioDevicesAdded_shouldActivateAddedDevice() {
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
- when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
- .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
-
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
- // Called twice, one after initiation, the other after onAudioDevicesAdded call.
- verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+ // The only added wired headset will be activated.
for (@MediaRecorder.Source int preset : PRESETS) {
- verify(audioManager, atLeast(2))
- .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+ verify(audioManager, atLeast(1))
+ .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // When adding multiple valid input devices, the last added device (usb headset in this
+ // case) will be activated.
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, never())
+ .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+ verify(audioManager, atLeast(1))
+ .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockHdmiInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // Do not activate since HDMI is not a valid input device.
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, never())
+ .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
}
}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 65b2275..00ae05c 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -76,6 +76,7 @@
"truth",
"Nene",
"Harrier",
+ "bedstead-enterprise",
],
libs: [
"android.test.base.stubs.system",
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e86e727..9cce431 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -20,6 +20,9 @@
import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
import static android.provider.Settings.System.RINGTONE;
+import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.workProfile;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.pm.PackageManager;
@@ -82,7 +85,7 @@
@RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
@EnsureHasWorkProfile
public void testSettings_workProfile() throws Exception {
- UserReference profile = sDeviceState.workProfile();
+ UserReference profile = workProfile(sDeviceState);
// Settings.Global settings are shared between different users
assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), profile.id());
@@ -96,7 +99,7 @@
@RequireRunOnInitialUser
@EnsureHasSecondaryUser
public void testSettings_secondaryUser() throws Exception {
- UserReference secondaryUser = sDeviceState.secondaryUser();
+ UserReference secondaryUser = secondaryUser(sDeviceState);
// Settings.Global settings are shared between different users
assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), secondaryUser.id());
@@ -223,7 +226,7 @@
@RequireRunOnInitialUser
@EnsureHasSecondaryUser
public void testSettings_stopAndRestartSecondaryUser() throws Exception {
- UserReference secondaryUser = sDeviceState.secondaryUser();
+ UserReference secondaryUser = secondaryUser(sDeviceState);
assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3c560fd..5f90b39 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -276,8 +276,6 @@
filegroup {
name: "SystemUI-tests-broken-robofiles-compile",
srcs: [
- "tests/src/**/*DeviceOnlyTest.java",
- "tests/src/**/*DeviceOnlyTest.kt",
"tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
"tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
"tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
@@ -293,7 +291,6 @@
"tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
"tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
"tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
- "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
"tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
"tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
"tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
@@ -303,14 +300,12 @@
"tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
"tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
"tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
- "tests/src/**/systemui/statusbar/commandline/CommandRegistryTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
"tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
"tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
"tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
"tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
"tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
- "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
"tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
"tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
"tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
@@ -449,27 +444,20 @@
"tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
"tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
"tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
- "tests/src/**/systemui/power/PowerUITest.java",
"tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
"tests/src/**/systemui/qs/QSImplTest.java",
- "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
- "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
"tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
"tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
- "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
- "tests/src/**/systemui/statusbar/CommandQueueTest.java",
- "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
- "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
- "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
- "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
- "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
- "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
- "tests/src/**/systemui/toast/ToastUITest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
+ "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+ "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
+ "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+ "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+ "tests/src/**/systemui/toast/ToastUITest.java",
],
visibility: ["//visibility:private"],
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0b18110..1c29db1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1148,6 +1148,13 @@
}
flag {
+ name: "media_controls_button_media3_placement"
+ namespace: "systemui"
+ description: "Use media3 API for action button placement preferences"
+ bug: "360196209"
+}
+
+flag {
name: "media_controls_drawables_reuse"
namespace: "systemui"
description: "Re-use created media drawables for media controls"
@@ -1552,6 +1559,16 @@
}
flag {
+ name: "hide_ringer_button_in_single_volume_mode"
+ namespace: "systemui"
+ description: "When the device is in single volume mode, hide the ringer button because it doesn't work"
+ bug: "374870615"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "qs_tile_detailed_view"
namespace: "systemui"
description: "Enables the tile detailed view UI."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8b0daf6..476cced 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -159,6 +159,7 @@
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.NoOpUpdate
+import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
@@ -796,6 +797,7 @@
}
if (viewModel.isEditMode && dragDropState != null) {
+ val isItemDragging = dragDropState.draggingItemKey == item.key
val outlineAlpha by
animateFloatAsState(
targetValue = if (selected) 1f else 0f,
@@ -812,13 +814,13 @@
enabled = selected,
alpha = { outlineAlpha },
modifier =
- Modifier.requiredSize(dpSize).thenIf(
- dragDropState.draggingItemKey != item.key
- ) {
- Modifier.animateItem(
- placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
- )
- },
+ Modifier.requiredSize(dpSize)
+ .thenIf(!isItemDragging) {
+ Modifier.animateItem(
+ placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
+ )
+ }
+ .thenIf(isItemDragging) { Modifier.zIndex(1f) },
onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) },
minHeightPx = widgetSizeInfo.minHeightPx,
maxHeightPx = widgetSizeInfo.maxHeightPx,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 900971b..2a85823 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -39,7 +39,7 @@
val resources: Resources,
private val hasStepClockAnimation: Boolean = false,
private val migratedClocks: Boolean = false,
- private val clockReactiveVariants: Boolean = false,
+ private val isClockReactiveVariantsEnabled: Boolean = false,
) : ClockProvider {
private var messageBuffers: ClockMessageBuffers? = null
@@ -54,7 +54,7 @@
throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return if (clockReactiveVariants) {
+ return if (isClockReactiveVariantsEnabled) {
val buffer =
messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
@@ -84,7 +84,7 @@
// TODO(b/352049256): Update placeholder to actual resource
resources.getDrawable(R.drawable.clock_default_thumbnail, null),
isReactiveToTone = true,
- isReactiveToTouch = clockReactiveVariants,
+ isReactiveToTouch = isClockReactiveVariantsEnabled,
axes = listOf(), // TODO: Ater some picker definition
)
}
@@ -103,7 +103,6 @@
timespec = DigitalTimespec.FIRST_DIGIT,
style =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
lineHeight = 147.25f,
fontVariation =
"'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -112,7 +111,6 @@
FontTextStyle(
fontVariation =
"'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
- fontFamily = "google_sans_flex.ttf",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -131,7 +129,6 @@
timespec = DigitalTimespec.SECOND_DIGIT,
style =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
lineHeight = 147.25f,
fontVariation =
"'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -140,7 +137,6 @@
FontTextStyle(
fontVariation =
"'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
- fontFamily = "google_sans_flex.ttf",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -159,7 +155,6 @@
timespec = DigitalTimespec.FIRST_DIGIT,
style =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
lineHeight = 147.25f,
fontVariation =
"'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -168,7 +163,6 @@
FontTextStyle(
fontVariation =
"'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
- fontFamily = "google_sans_flex.ttf",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -187,7 +181,6 @@
timespec = DigitalTimespec.SECOND_DIGIT,
style =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
lineHeight = 147.25f,
fontVariation =
"'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
@@ -196,7 +189,6 @@
FontTextStyle(
fontVariation =
"'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
- fontFamily = "google_sans_flex.ttf",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -221,13 +213,11 @@
timespec = DigitalTimespec.TIME_FULL_FORMAT,
style =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
fontVariation = "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100",
fontSizeScale = 0.98f,
),
aodStyle =
FontTextStyle(
- fontFamily = "google_sans_flex.ttf",
fontVariation = "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index af3ddfc..29ee874 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
@@ -27,6 +30,7 @@
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.inputmethod.data.model.InputMethodModel
import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
@@ -67,11 +71,12 @@
private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor }
private val isInputEnabled = MutableStateFlow(true)
- private val underTest =
+ private val underTest by lazy {
kosmos.passwordBouncerViewModelFactory.create(
isInputEnabled = isInputEnabled,
onIntentionalUserInput = {},
)
+ }
@Before
fun setUp() {
@@ -345,6 +350,37 @@
assertThat(textInputFocusRequested).isFalse()
}
+ @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+ @Test
+ fun consumeConfirmKeyEvents_toPreventItFromPropagating() =
+ testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = true) }
+
+ @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+ @EnableSceneContainer
+ @Test
+ fun noops_whenSceneContainerIsAlsoEnabled() =
+ testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = false) }
+
+ private fun verifyConfirmKeyEventsBehavior(keyUpEventConsumed: Boolean) {
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_DPAD_CENTER))
+ .isFalse()
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_DPAD_CENTER))
+ .isEqualTo(keyUpEventConsumed)
+
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_ENTER)).isFalse()
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_ENTER))
+ .isEqualTo(keyUpEventConsumed)
+
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_NUMPAD_ENTER))
+ .isFalse()
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_NUMPAD_ENTER))
+ .isEqualTo(keyUpEventConsumed)
+
+ // space is ignored.
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_SPACE)).isFalse()
+ assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_SPACE)).isFalse()
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
index 855b6d0..587d3d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt
@@ -20,6 +20,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.VibratorHelper
@@ -141,11 +142,7 @@
}
private fun createPlugin() {
- plugin =
- SeekbarHapticPlugin(
- vibratorHelper,
- kosmos.fakeSystemClock,
- )
+ plugin = SeekbarHapticPlugin(vibratorHelper, kosmos.msdlPlayer, kosmos.fakeSystemClock)
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 28f88fe..3467382 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -16,16 +16,26 @@
package com.android.systemui.haptics.slider
+import android.os.VibrationAttributes
import android.os.VibrationEffect
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.fakeVibratorHelper
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.test.assertEquals
import kotlin.test.assertTrue
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +45,7 @@
class SliderHapticFeedbackProviderTest : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val config = SliderHapticFeedbackConfig()
@@ -44,7 +55,14 @@
private val dragTextureThresholdMillis =
lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
private val vibratorHelper = kosmos.fakeVibratorHelper
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider
+ private val pipeliningAttributes =
+ VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+ .build()
+ private lateinit var dynamicProperties: InteractionProperties.DynamicVibrationScale
@Before
fun setup() {
@@ -54,13 +72,20 @@
sliderHapticFeedbackProvider =
SliderHapticFeedbackProvider(
vibratorHelper,
+ msdlPlayer,
dragVelocityProvider,
config,
kosmos.fakeSystemClock,
)
+ dynamicProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+ pipeliningAttributes,
+ )
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtLowerBookend_playsClick() =
with(kosmos) {
val vibration =
@@ -77,6 +102,18 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtLowerBookend_playsDragThresholdLimitToken() =
+ testScope.runTest {
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertThat(msdlPlayer.latestTokenPlayed)
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
with(kosmos) {
val vibration =
@@ -94,6 +131,20 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtLowerBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
+ testScope.runTest {
+ sliderHapticFeedbackProvider.onLowerBookend()
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertThat(msdlPlayer.latestTokenPlayed)
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtUpperBookend_playsClick() =
with(kosmos) {
val vibration =
@@ -110,6 +161,18 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtUpperBookend_playsDragThresholdLimitToken() =
+ testScope.runTest {
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertThat(msdlPlayer.latestTokenPlayed)
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
with(kosmos) {
val vibration =
@@ -127,6 +190,20 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtUpperBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
+ testScope.runTest {
+ sliderHapticFeedbackProvider.onUpperBookend()
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertThat(msdlPlayer.latestTokenPlayed)
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
with(kosmos) {
// GIVEN max velocity and slider progress
@@ -150,6 +227,31 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtProgress_onQuickSuccession_playsContinuousDragTokenOnce() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(config.maxVelocityToScale, progress)
+ val expectedProperties =
+ InteractionProperties.DynamicVibrationScale(expectedScale, pipeliningAttributes)
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN two calls to play occur immediately
+ sliderHapticFeedbackProvider.onProgress(progress)
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // THEN the correct token plays once
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
with(kosmos) {
// GIVEN max velocity and a slider progress at half progress
@@ -175,6 +277,41 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtProgress_beforeNextDragThreshold_playsContinousDragTokenOnce() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+
+ // Given a second slider progress event smaller than the progress threshold
+ val secondProgress =
+ firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and
+ // progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN Only the first event plays the expected token and propertiesv
+ val expectedProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ firstProgress,
+ ),
+ pipeliningAttributes,
+ )
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
with(kosmos) {
// GIVEN max velocity and a slider progress at half progress
@@ -200,6 +337,51 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtProgress_afterNextDragThreshold_playsContinuousDragTokenTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+
+ // Given a second slider progress event beyond progress threshold
+ val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and
+ // progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN the correct token plays twice with the correct properties
+ val firstProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ firstProgress,
+ ),
+ pipeliningAttributes,
+ )
+ val secondProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ secondProgress,
+ ),
+ pipeliningAttributes,
+ )
+
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+ assertThat(msdlPlayer.tokensPlayed[0]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+ assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(firstProperties)
+ assertThat(msdlPlayer.tokensPlayed[1]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
+ assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(secondProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
with(kosmos) {
// GIVEN max velocity and slider progress
@@ -233,6 +415,36 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTokensTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+ pipeliningAttributes,
+ )
+
+ // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
+ sliderHapticFeedbackProvider.onLowerBookend()
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN a vibration is to trigger again at the lower bookend
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ // THEN there are two bookend token vibrations
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+ assertThat(msdlPlayer.tokensPlayed[0])
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
+ assertThat(msdlPlayer.tokensPlayed[1])
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
with(kosmos) {
// GIVEN max velocity and slider progress
@@ -265,6 +477,36 @@
)
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTokensTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedProperties =
+ InteractionProperties.DynamicVibrationScale(
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+ pipeliningAttributes,
+ )
+
+ // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
+ sliderHapticFeedbackProvider.onUpperBookend()
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN a vibration is to trigger again at the upper bookend
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ // THEN there are two bookend vibrations
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
+ assertThat(msdlPlayer.tokensPlayed[0])
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
+ assertThat(msdlPlayer.tokensPlayed[1])
+ .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
+ assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
+ }
+
+ @Test
fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
with(kosmos) {
// GIVEN max velocity and a slider progress at half progress
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 93754fd..77f1979 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -314,16 +314,6 @@
}
@Test
- fun isActiveDreamLockscreenHosted() =
- testScope.runTest {
- underTest.setIsActiveDreamLockscreenHosted(true)
- assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(true)
-
- underTest.setIsActiveDreamLockscreenHosted(false)
- assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(false)
- }
-
- @Test
fun isUdfpsSupported() =
testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
@@ -336,26 +326,14 @@
@Test
fun isKeyguardGoingAway() =
testScope.runTest {
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
- runCurrent()
- assertThat(latest).isFalse()
+ val isGoingAway by collectLastValue(underTest.isKeyguardGoingAway)
+ assertThat(isGoingAway).isFalse()
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
+ underTest.isKeyguardGoingAway.value = true
+ assertThat(isGoingAway).isTrue()
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
- captor.value.onKeyguardGoingAwayChanged()
- runCurrent()
- assertThat(latest).isTrue()
-
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
- captor.value.onKeyguardGoingAwayChanged()
- runCurrent()
- assertThat(latest).isFalse()
-
- job.cancel()
+ underTest.isKeyguardGoingAway.value = false
+ assertThat(isGoingAway).isFalse()
}
@Test
@@ -423,17 +401,17 @@
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_REQUEST_PULSE,
- DozeMachine.State.DOZE_PULSING
+ DozeMachine.State.DOZE_PULSING,
)
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
- DozeMachine.State.DOZE_PULSE_DONE
+ DozeMachine.State.DOZE_PULSE_DONE,
)
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_AOD_PAUSING,
- DozeMachine.State.DOZE_AOD_PAUSED
+ DozeMachine.State.DOZE_AOD_PAUSED,
)
runCurrent()
@@ -443,22 +421,22 @@
// Initial value will be UNINITIALIZED
DozeTransitionModel(
DozeStateModel.UNINITIALIZED,
- DozeStateModel.UNINITIALIZED
+ DozeStateModel.UNINITIALIZED,
),
DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
DozeTransitionModel(
DozeStateModel.DOZE_REQUEST_PULSE,
- DozeStateModel.DOZE_PULSING
+ DozeStateModel.DOZE_PULSING,
),
DozeTransitionModel(
DozeStateModel.DOZE_SUSPEND_TRIGGERS,
- DozeStateModel.DOZE_PULSE_DONE
+ DozeStateModel.DOZE_PULSE_DONE,
),
DozeTransitionModel(
DozeStateModel.DOZE_AOD_PAUSING,
- DozeStateModel.DOZE_AOD_PAUSED
+ DozeStateModel.DOZE_AOD_PAUSED,
),
)
)
@@ -510,12 +488,7 @@
// consuming it should handle that properly.
assertThat(values).isEqualTo(listOf(null))
- listOf(
- Point(500, 500),
- Point(0, 0),
- null,
- Point(250, 250),
- )
+ listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
.onEach {
facePropertyRepository.setSensorLocation(it)
runCurrent()
@@ -551,7 +524,7 @@
.onEach { biometricSourceType ->
underTest.setBiometricUnlockState(
BiometricUnlockMode.NONE,
- BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+ BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType),
)
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 3b6e5d0..df44425 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -90,6 +90,7 @@
private lateinit var powerInteractor: PowerInteractor
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var keyguardInteractor: KeyguardInteractor
companion object {
@JvmStatic
@@ -106,6 +107,7 @@
@Before
fun setup() {
powerInteractor = kosmos.powerInteractor
+ keyguardInteractor = kosmos.keyguardInteractor
transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
underTest = kosmos.fromDozingTransitionInteractor
@@ -137,6 +139,20 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeup_whenGoingAway() =
+ testScope.runTest {
+ keyguardInteractor.setIsKeyguardGoingAway(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(60L)
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 4862104..e60d971 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -173,17 +173,6 @@
@Test
@EnableSceneContainer
- fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
- testScope.runTest {
- val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeLayoutWide(true)
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
- kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- assertThat(value).isTrue()
- }
-
- @Test
- @EnableSceneContainer
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index 945e44a..fbdab7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -53,6 +54,7 @@
@RunWith(AndroidJUnit4::class)
class KeyguardKeyEventInteractorTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
private val actionDownVolumeDownKeyEvent =
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
@@ -85,6 +87,7 @@
mediaSessionLegacyHelperWrapper,
backActionInteractor,
powerInteractor,
+ kosmos.keyguardMediaKeyInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt
new file mode 100644
index 0000000..b82e154
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
+import android.view.KeyEvent.KEYCODE_MEDIA_PAUSE
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.fakeAudioRepository
+import com.google.common.truth.Correspondence.transforming
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMPOSE_BOUNCER)
+class KeyguardMediaKeyInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = kosmos.keyguardMediaKeyInteractor
+
+ @Before
+ fun setup() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun test_onKeyEvent_playPauseKeyEvents_areSkipped_whenACallIsActive() =
+ testScope.runTest {
+ kosmos.fakeTelephonyRepository.setIsInCall(true)
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY))
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PAUSE))
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY_PAUSE))
+
+ assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents).isEmpty()
+ }
+
+ @Test
+ fun test_onKeyEvent_playPauseKeyEvents_areNotSkipped_whenACallIsNotActive() =
+ testScope.runTest {
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
+
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PAUSE))
+ assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PAUSE))
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY))
+ assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PLAY))
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KEYCODE_MEDIA_PLAY_PAUSE))
+ assertEventConsumed(KeyEvent(ACTION_UP, KEYCODE_MEDIA_PLAY_PAUSE))
+
+ assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents)
+ .comparingElementsUsing<KeyEvent, Pair<Int, Int>>(
+ transforming({ Pair(it!!.action, it.keyCode) }, "action and keycode")
+ )
+ .containsExactly(
+ Pair(ACTION_UP, KEYCODE_MEDIA_PAUSE),
+ Pair(ACTION_UP, KEYCODE_MEDIA_PLAY),
+ Pair(ACTION_UP, KEYCODE_MEDIA_PLAY_PAUSE),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun test_onKeyEvent_nonPlayPauseKeyEvents_areNotSkipped_whenACallIsActive() =
+ testScope.runTest {
+ kosmos.fakeTelephonyRepository.setIsInCall(true)
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MUTE))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MUTE))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_REWIND))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_RECORD))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_RECORD))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
+
+ assertEventConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK))
+ assertEventConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK))
+
+ assertThat(kosmos.fakeAudioRepository.dispatchedKeyEvents)
+ .comparingElementsUsing<KeyEvent, Pair<Int, Int>>(
+ transforming({ Pair(it!!.action, it.keyCode) }, "action and keycode")
+ )
+ .containsExactly(
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MUTE),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MUTE),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_REWIND),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_RECORD),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_RECORD),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD),
+ Pair(ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK),
+ Pair(ACTION_UP, KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun volumeKeyEvents_keyEvents_areSkipped() =
+ testScope.runTest {
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
+
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP))
+ assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP))
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN))
+ assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_DOWN))
+ assertEventNotConsumed(KeyEvent(ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE))
+ assertEventNotConsumed(KeyEvent(ACTION_UP, KeyEvent.KEYCODE_VOLUME_MUTE))
+ }
+
+ private fun assertEventConsumed(keyEvent: KeyEvent) {
+ assertThat(underTest.processMediaKeyEvent(keyEvent)).isTrue()
+ }
+
+ private fun assertEventNotConsumed(keyEvent: KeyEvent) {
+ assertThat(underTest.processMediaKeyEvent(keyEvent)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
deleted file mode 100644
index 28d53c7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-
-internal class FakeScreenshotPolicy : ScreenshotPolicy {
-
- private val userTypes = mutableMapOf<Int, Boolean>()
- private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()
-
- fun setManagedProfile(userId: Int, managedUser: Boolean) {
- userTypes[userId] = managedUser
- }
- override suspend fun isManagedProfile(userId: Int): Boolean {
- return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
- }
-
- fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
- this.contentInfo[userId] = contentInfo
- }
-
- override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
- return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index fb91c78..080f46f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.google.android.msdl.domain.MSDLPlayer
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -46,34 +47,26 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.notNull
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class BrightnessSliderControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var brightnessSliderView: BrightnessSliderView
- @Mock
- private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
- @Mock
- private lateinit var mirrorController: BrightnessMirrorController
- @Mock
- private lateinit var mirror: ToggleSlider
- @Mock
- private lateinit var motionEvent: MotionEvent
- @Mock
- private lateinit var listener: ToggleSlider.Listener
- @Mock
- private lateinit var vibratorHelper: VibratorHelper
- @Mock
- private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var brightnessSliderView: BrightnessSliderView
+ @Mock private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
+ @Mock private lateinit var mirrorController: BrightnessMirrorController
+ @Mock private lateinit var mirror: ToggleSlider
+ @Mock private lateinit var motionEvent: MotionEvent
+ @Mock private lateinit var listener: ToggleSlider.Listener
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var msdlPlayer: MSDLPlayer
+ @Mock private lateinit var activityStarter: ActivityStarter
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
- @Mock
- private lateinit var seekBar: SeekBar
+ @Mock private lateinit var seekBar: SeekBar
private val uiEventLogger = UiEventLoggerFake()
private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
private val systemClock = FakeSystemClock()
@@ -93,7 +86,7 @@
brightnessSliderView,
mFalsingManager,
uiEventLogger,
- SeekbarHapticPlugin(vibratorHelper, systemClock),
+ SeekbarHapticPlugin(vibratorHelper, msdlPlayer, systemClock),
activityStarter,
)
mController.init()
@@ -241,4 +234,4 @@
assertThat(uiEventLogger.numLogs()).isEqualTo(1)
assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
deleted file mode 100644
index 2ac0ed0..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2023 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.shade
-
-import android.os.PowerManager
-import android.view.MotionEvent
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
-class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
- @Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var falsingCollector: FalsingCollector
- @Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var shadeLogger: ShadeLogger
- @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
-
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var underTest: LockscreenHostedDreamGestureListener
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- powerRepository = FakePowerRepository()
- keyguardRepository = FakeKeyguardRepository()
-
- underTest =
- LockscreenHostedDreamGestureListener(
- falsingManager,
- PowerInteractorFactory.create(
- repository = powerRepository,
- statusBarStateController = statusBarStateController,
- )
- .powerInteractor,
- statusBarStateController,
- primaryBouncerInteractor,
- keyguardRepository,
- shadeLogger,
- )
- whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
- whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
- }
-
- @Test
- fun testGestureDetector_onSingleTap_whileDreaming() =
- testScope.runTest {
- // GIVEN device dreaming and the dream is hosted in lockscreen
- whenever(statusBarStateController.isDreaming).thenReturn(true)
- keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- testScope.runCurrent()
-
- // GIVEN the falsing manager does NOT think the tap is a false tap
- whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
- // WHEN there's a tap
- underTest.onSingleTapUp(upEv)
-
- // THEN wake up device if dreaming
- Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
- Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
- }
-
- @Test
- fun testGestureDetector_onSingleTap_notOnKeyguard() =
- testScope.runTest {
- // GIVEN device dreaming and the dream is hosted in lockscreen
- whenever(statusBarStateController.isDreaming).thenReturn(true)
- keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- testScope.runCurrent()
-
- // GIVEN shade is open
- whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
-
- // GIVEN the falsing manager does NOT think the tap is a false tap
- whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
- // WHEN there's a tap
- underTest.onSingleTapUp(upEv)
-
- // THEN the falsing manager never gets a call
- verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
- }
-
- @Test
- fun testGestureDetector_onSingleTap_bouncerShown() =
- testScope.runTest {
- // GIVEN device dreaming and the dream is hosted in lockscreen
- whenever(statusBarStateController.isDreaming).thenReturn(true)
- keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- testScope.runCurrent()
-
- // GIVEN bouncer is expanded
- whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
-
- // GIVEN the falsing manager does NOT think the tap is a false tap
- whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
-
- // WHEN there's a tap
- underTest.onSingleTapUp(upEv)
-
- // THEN the falsing manager never gets a call
- verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
- }
-
- @Test
- fun testGestureDetector_onSingleTap_falsing() =
- testScope.runTest {
- // GIVEN device dreaming and the dream is hosted in lockscreen
- whenever(statusBarStateController.isDreaming).thenReturn(true)
- keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- testScope.runCurrent()
-
- // GIVEN the falsing manager thinks the tap is a false tap
- whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
-
- // WHEN there's a tap
- underTest.onSingleTapUp(upEv)
-
- // THEN the device doesn't wake up
- Truth.assertThat(powerRepository.lastWakeWhy).isNull()
- Truth.assertThat(powerRepository.lastWakeReason).isNull()
- }
-
- @Test
- fun testSingleTap_notDreaming_noFalsingCheck() =
- testScope.runTest {
- // GIVEN device not dreaming with lockscreen hosted dream
- whenever(statusBarStateController.isDreaming).thenReturn(false)
- keyguardRepository.setIsActiveDreamLockscreenHosted(false)
- testScope.runCurrent()
-
- // WHEN there's a tap
- underTest.onSingleTapUp(upEv)
-
- // THEN the falsing manager never gets a call
- verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
- }
-}
-
-private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
index 663c341..16da3d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
@@ -70,14 +70,14 @@
}
private fun createController() =
- PrivacyDotViewController(
+ PrivacyDotViewControllerImpl(
executor,
testScope.backgroundScope,
statusBarStateController,
configurationController,
contentInsetsProvider,
animationScheduler = mock<SystemStatusAnimationScheduler>(),
- shadeInteractor = null
+ shadeInteractor = null,
)
.also { it.setUiExecutor(executor) }
@@ -307,7 +307,7 @@
newTopLeftView,
newTopRightView,
newBottomLeftView,
- newBottomRightView
+ newBottomRightView,
)
assertThat((newBottomRightView.layoutParams as FrameLayout.LayoutParams).gravity)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index dc9c22f..f1edb41 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -21,6 +21,7 @@
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -36,9 +37,11 @@
import static java.util.Objects.requireNonNull;
+import android.app.Flags;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import androidx.annotation.NonNull;
@@ -61,6 +64,7 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -69,6 +73,8 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.util.settings.SecureSettings;
@@ -82,10 +88,12 @@
import org.mockito.Spy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -93,6 +101,7 @@
public class PreparationCoordinatorTest extends SysuiTestCase {
private NotifCollectionListener mCollectionListener;
private OnBeforeFinalizeFilterListener mBeforeFilterListener;
+ private OnBeforeTransformGroupsListener mBeforeTransformGroupsListener;
private NotifFilter mUninflatedFilter;
private NotifFilter mInflationErrorFilter;
private NotifInflationErrorManager mErrorManager;
@@ -101,6 +110,8 @@
@Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor;
@Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor;
+ @Captor private ArgumentCaptor<OnBeforeTransformGroupsListener>
+ mBeforeTransformGroupsListenerCaptor;
@Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor;
@Mock private NotifSectioner mNotifSectioner;
@@ -108,13 +119,14 @@
@Mock private NotifPipeline mNotifPipeline;
@Mock private IStatusBarService mService;
@Mock private BindEventManagerImpl mBindEventManagerImpl;
+ @Mock private AppIconProvider mAppIconProvider;
+ @Mock private NotificationIconStyleProvider mNotificationIconStyleProvider;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
@Mock private Handler mHandler;
@Mock private SecureSettings mSecureSettings;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
- @Mock
- HighPriorityProvider mHighPriorityProvider;
+ @Mock HighPriorityProvider mHighPriorityProvider;
private SectionStyleProvider mSectionStyleProvider;
@Mock private UserTracker mUserTracker;
@Mock private GroupMembershipManager mGroupMembershipManager;
@@ -126,6 +138,11 @@
return new NotificationEntryBuilder().setSection(mNotifSection);
}
+ @NonNull
+ private GroupEntryBuilder getGroupEntryBuilder() {
+ return new GroupEntryBuilder().setSection(mNotifSection);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -138,7 +155,7 @@
mSectionStyleProvider,
mUserTracker,
mGroupMembershipManager
- );
+ );
mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
@@ -153,6 +170,8 @@
mAdjustmentProvider,
mService,
mBindEventManagerImpl,
+ mAppIconProvider,
+ mNotificationIconStyleProvider,
TEST_CHILD_BIND_CUTOFF,
TEST_MAX_GROUP_DELAY);
@@ -163,6 +182,15 @@
mInflationErrorFilter = filters.get(0);
mUninflatedFilter = filters.get(1);
+ if (android.app.Flags.notificationsRedesignAppIcons()) {
+ verify(mNotifPipeline).addOnBeforeTransformGroupsListener(
+ mBeforeTransformGroupsListenerCaptor.capture());
+ mBeforeTransformGroupsListener = mBeforeTransformGroupsListenerCaptor.getValue();
+ } else {
+ verify(mNotifPipeline, never()).addOnBeforeTransformGroupsListener(
+ mBeforeTransformGroupsListenerCaptor.capture());
+ }
+
verify(mNotifPipeline).addCollectionListener(mCollectionListenerCaptor.capture());
mCollectionListener = mCollectionListenerCaptor.getValue();
@@ -199,6 +227,100 @@
}
@Test
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS)
+ public void testPurgesAppIconProviderCache() {
+ // GIVEN a notification list
+ NotificationEntry entry1 = getNotificationEntryBuilder().setPkg("1").build();
+ NotificationEntry entry2 = getNotificationEntryBuilder().setPkg("2").build();
+ NotificationEntry entry2bis = getNotificationEntryBuilder().setPkg("2").build();
+ NotificationEntry entry3 = getNotificationEntryBuilder().setPkg("3").build();
+
+ String groupKey1 = "group1";
+ NotificationEntry summary =
+ getNotificationEntryBuilder()
+ .setPkg(groupKey1)
+ .setGroup(mContext, groupKey1)
+ .setGroupSummary(mContext, true)
+ .build();
+ NotificationEntry child1 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+ .setPkg(groupKey1).build();
+ NotificationEntry child2 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+ .setPkg(groupKey1).build();
+ GroupEntry groupWithSummaryAndChildren = getGroupEntryBuilder().setKey(groupKey1)
+ .setSummary(summary).addChild(child1).addChild(child2).build();
+
+ String groupKey2 = "group2";
+ NotificationEntry summary2 =
+ getNotificationEntryBuilder()
+ .setPkg(groupKey2)
+ .setGroup(mContext, groupKey2)
+ .setGroupSummary(mContext, true)
+ .build();
+ GroupEntry summaryOnlyGroup = getGroupEntryBuilder().setKey(groupKey2)
+ .setSummary(summary2).build();
+
+ // WHEN onBeforeTransformGroup is called
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(
+ List.of(entry1, entry2, entry2bis, entry3,
+ groupWithSummaryAndChildren, summaryOnlyGroup));
+
+ // THEN purge should be called
+ ArgumentCaptor<Collection<String>> argumentCaptor = ArgumentCaptor.forClass(List.class);
+ verify(mAppIconProvider).purgeCache(argumentCaptor.capture());
+ List<String> actualList = argumentCaptor.getValue().stream().sorted().toList();
+ List<String> expectedList = Stream.of("1", "2", "3", "group1", "group2")
+ .sorted().toList();
+ assertEquals(expectedList, actualList);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS)
+ public void testPurgesNotificationIconStyleProviderCache() {
+ // GIVEN a notification list
+ NotificationEntry entry1 = getNotificationEntryBuilder().setPkg("1").build();
+ NotificationEntry entry2 = getNotificationEntryBuilder().setPkg("2").build();
+ NotificationEntry entry2bis = getNotificationEntryBuilder().setPkg("2").build();
+ NotificationEntry entry3 = getNotificationEntryBuilder().setPkg("3").build();
+
+ String groupKey1 = "group1";
+ NotificationEntry summary =
+ getNotificationEntryBuilder()
+ .setPkg(groupKey1)
+ .setGroup(mContext, groupKey1)
+ .setGroupSummary(mContext, true)
+ .build();
+ NotificationEntry child1 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+ .setPkg(groupKey1).build();
+ NotificationEntry child2 = getNotificationEntryBuilder().setGroup(mContext, groupKey1)
+ .setPkg(groupKey1).build();
+ GroupEntry groupWithSummaryAndChildren = getGroupEntryBuilder().setKey(groupKey1)
+ .setSummary(summary).addChild(child1).addChild(child2).build();
+
+ String groupKey2 = "group2";
+ NotificationEntry summary2 =
+ getNotificationEntryBuilder()
+ .setPkg(groupKey2)
+ .setGroup(mContext, groupKey2)
+ .setGroupSummary(mContext, true)
+ .build();
+ GroupEntry summaryOnlyGroup = getGroupEntryBuilder().setKey(groupKey2)
+ .setSummary(summary2).build();
+
+ // WHEN onBeforeTransformGroup is called
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(
+ List.of(entry1, entry2, entry2bis, entry3,
+ groupWithSummaryAndChildren, summaryOnlyGroup));
+
+ // THEN purge should be called
+ ArgumentCaptor<Collection<String>> argumentCaptor = ArgumentCaptor.forClass(List.class);
+ verify(mNotificationIconStyleProvider).purgeCache(argumentCaptor.capture());
+ List<String> actualList = argumentCaptor.getValue().stream().sorted().toList();
+ List<String> expectedList = Stream.of("1", "2", "3", "group1", "group2")
+ .sorted().toList();
+ assertEquals(expectedList, actualList);
+ }
+
+ @Test
public void testInflatesNewNotification() {
// WHEN there is a new notification
mCollectionListener.onEntryInit(mEntry);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index cfac486..ea5c29e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,7 +65,6 @@
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
@@ -100,7 +98,6 @@
@Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Mock private VisualStabilityCoordinatorLogger mLogger;
- @Mock private KeyguardStateController mKeyguardStateController;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -141,7 +138,6 @@
mKosmos.getCommunalSceneInteractor(),
mKosmos.getShadeInteractor(),
mKosmos.getKeyguardTransitionInteractor(),
- mKeyguardStateController,
mLogger);
mCoordinator.attach(mNotifPipeline);
mTestScope.getTestScheduler().runCurrent();
@@ -526,13 +522,23 @@
@EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
public void testNotLockscreenInGoneTransition_invalidationCalled() {
// GIVEN visual stability is being maintained b/c animation is playing
- doReturn(true).when(mKeyguardStateController).isKeyguardFadingAway();
- mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.RUNNING), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
// WHEN the animation has stopped playing
- doReturn(false).when(mKeyguardStateController).isKeyguardFadingAway();
- mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.FINISHED), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
// invalidate is called, b/c we were previously suppressing the pipeline from running
verifyStabilityManagerWasInvalidated(times(1));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index aed9af6..406ca05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -33,6 +33,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -67,6 +68,8 @@
@Mock
private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Mock
+ private Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
+ @Mock
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private KeyguardUpdateMonitorLogger mLogger;
@@ -86,6 +89,7 @@
mKeyguardUnlockAnimationControllerLazy,
mLogger,
mDumpManager,
+ mKeyguardInteractorLazy,
mFeatureFlags,
mSelectedUserInteractor);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
new file mode 100644
index 0000000..7d55599
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.volume.dialog.ringer.domain
+
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogRingerInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val controller = kosmos.fakeVolumeDialogController
+
+ private lateinit var underTest: VolumeDialogRingerInteractor
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.volumeDialogRingerInteractor
+ controller.setStreamVolume(STREAM_RING, 50)
+ }
+
+ @Test
+ fun setRingerMode_normal() =
+ testScope.runTest {
+ runCurrent()
+ val ringerModel by collectLastValue(underTest.ringerModel)
+
+ underTest.setRingerMode(RingerMode(RINGER_MODE_NORMAL))
+ controller.getState()
+ runCurrent()
+
+ assertThat(ringerModel).isNotNull()
+ assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_NORMAL))
+ }
+
+ @Test
+ fun setRingerMode_silent() =
+ testScope.runTest {
+ runCurrent()
+ val ringerModel by collectLastValue(underTest.ringerModel)
+
+ underTest.setRingerMode(RingerMode(RINGER_MODE_SILENT))
+ controller.getState()
+ runCurrent()
+
+ assertThat(ringerModel).isNotNull()
+ assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_SILENT))
+ }
+
+ @Test
+ fun setRingerMode_vibrate() =
+ testScope.runTest {
+ runCurrent()
+ val ringerModel by collectLastValue(underTest.ringerModel)
+
+ underTest.setRingerMode(RingerMode(RINGER_MODE_VIBRATE))
+ controller.getState()
+ runCurrent()
+
+ assertThat(ringerModel).isNotNull()
+ assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_VIBRATE))
+ }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cdf15ca..c494e85 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3745,6 +3745,10 @@
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
[CHAR LIMIT=NONE] -->
<string name="shortcut_helper_content_description_drag_handle">Drag handle</string>
+ <!-- Label on the keyboard settings button in keyboard shortcut helper, that allows user to
+ open keyboard settings while in shortcut helper. The helper is a component that shows the
+ user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_keyboard_settings_buttons_label">Keyboard Settings</string>
<!-- Keyboard touchpad tutorial scheduler-->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 83ab524..b3ea75d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2368,7 +2368,7 @@
}
// Take a guess at initial SIM state, battery status and PLMN until we get an update
- mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ 100, /* plugged= */
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ -1, /* plugged= */
0, CHARGING_POLICY_DEFAULT, /* maxChargingWattage= */0, /* present= */true);
// Watch for interesting updates
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8ae11ab..811b47d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -43,7 +43,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.util.NotificationChannels;
import java.lang.reflect.InvocationTargetException;
@@ -454,13 +454,13 @@
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (mServicesStarted) {
- ConfigurationController configController = mSysUIComponent.getConfigurationController();
+ ConfigurationForwarder configForwarder = mSysUIComponent.getConfigurationForwarder();
if (Trace.isEnabled()) {
Trace.traceBegin(
Trace.TRACE_TAG_APP,
- configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ configForwarder.getClass().getSimpleName() + ".onConfigurationChanged()");
}
- configController.onConfigurationChanged(newConfig);
+ configForwarder.onConfigurationChanged(newConfig);
Trace.endSection();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt
new file mode 100644
index 0000000..e3cf88c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/startable/BouncerStartable.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.bouncer.domain.startable
+
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Starts interactors needed for the compose bouncer to work as expected. */
+@SysUISingleton
+class BouncerStartable
+@Inject
+constructor(
+ private val keyguardMediaKeyInteractor: dagger.Lazy<KeyguardMediaKeyInteractor>,
+ @Application private val scope: CoroutineScope,
+) : CoreStartable {
+ override fun start() {
+ if (!ComposeBouncerFlags.isEnabled) return
+
+ scope.launchTraced("KeyguardMediaKeyInteractor#start") {
+ keyguardMediaKeyInteractor.get().activate()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index 7647cf6..47570a5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -43,17 +43,25 @@
fun isUnexpectedlyInLegacyMode() =
RefactorFlagUtils.isUnexpectedlyInLegacyMode(
isEnabled,
- "SceneContainerFlag || ComposeBouncerFlag"
+ "SceneContainerFlag || ComposeBouncerFlag",
)
/**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ fun assertInLegacyMode() =
+ RefactorFlagUtils.assertInLegacyMode(isEnabled, "SceneContainerFlag || ComposeBouncerFlag")
+
+ /**
* Returns `true` if only compose bouncer is enabled and scene container framework is not
* enabled.
*/
@Deprecated(
"Avoid using this, this is meant to be used only by the glue code " +
"that includes compose bouncer in legacy keyguard.",
- replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
+ replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()"),
)
fun isOnlyComposeBouncerEnabled(): Boolean {
return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 148b9ea..1bcc1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.annotation.StringRes
+import androidx.compose.ui.input.key.KeyEventType
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -122,6 +123,13 @@
/** Invoked after a successful authentication. */
protected open fun onSuccessfulAuthentication() = Unit
+ /**
+ * Invoked for any key events on the bouncer.
+ *
+ * @return whether the event was consumed by this method and should not be propagated further.
+ */
+ open fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean = false
+
/** Perform authentication result haptics */
private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
if (result == AuthenticationResult.SKIPPED) return
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 5f97391..67d312e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -34,6 +34,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.assisted.AssistedFactory
@@ -63,6 +64,7 @@
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
private val bouncerHapticPlayer: BouncerHapticPlayer,
+ private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -337,10 +339,9 @@
* @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
*/
fun onKeyEvent(keyEvent: KeyEvent): Boolean {
- return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
- keyEvent.type,
- keyEvent.nativeKeyEvent.keyCode,
- ) ?: false
+ if (keyguardMediaKeyInteractor.processMediaKeyEvent(keyEvent.nativeKeyEvent)) return true
+ return authMethodViewModel.value?.onKeyEvent(keyEvent.type, keyEvent.nativeKeyEvent.keyCode)
+ ?: false
}
data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1427d78..b8c6ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,9 +16,12 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.view.KeyEvent
import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.input.key.KeyEventType
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -150,6 +153,17 @@
return _password.value.toCharArray().toList()
}
+ override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
+ // Ignore SPACE as a confirm key to allow the space character within passwords.
+ val isKeyboardEnterKey =
+ KeyEvent.isConfirmKey(keyCode) &&
+ keyCode != KeyEvent.KEYCODE_SPACE &&
+ type == KeyEventType.KeyUp
+ // consume confirm key events while on the bouncer. This prevents it from propagating
+ // and avoids other parent elements from receiving it.
+ return isKeyboardEnterKey && ComposeBouncerFlags.isOnlyComposeBouncerEnabled()
+ }
+
override fun onSuccessfulAuthentication() {
wasSuccessfullyAuthenticated = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 0cb4260..5c8a9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -251,7 +251,7 @@
*
* @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
*/
- fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
+ override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
return when (type) {
KeyEventType.KeyUp -> {
if (isConfirmKey(keyCode)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 3fe6669..17f1961 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -29,6 +29,7 @@
import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
@@ -125,13 +126,20 @@
BootCompleteCacheImpl provideBootCacheImpl();
/**
- * Creates a ContextComponentHelper.
+ * Creates a ConfigurationController.
*/
@SysUISingleton
@GlobalConfig
ConfigurationController getConfigurationController();
/**
+ * Creates a ConfigurationForwarder.
+ */
+ @SysUISingleton
+ @GlobalConfig
+ ConfigurationForwarder getConfigurationForwarder();
+
+ /**
* Creates a ContextComponentHelper.
*/
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 0de919d..6fb6236 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
+import com.android.systemui.bouncer.domain.startable.BouncerStartable
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
@@ -304,6 +305,11 @@
@Binds
@IntoMap
+ @ClassKey(BouncerStartable::class)
+ abstract fun bindBouncerStartable(impl: BouncerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 21922ff..12718e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -17,6 +17,7 @@
package com.android.systemui.doze;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.Flags.screenOffUnlockUdfps;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
@@ -248,8 +249,8 @@
true /* touchscreen */,
false /* ignoresSetting */,
dozeParameters.longPressUsesProx(),
- false /* immediatelyReRegister */,
- true /* requiresAod */
+ screenOffUnlockUdfps() /* immediatelyReRegister */,
+ !screenOffUnlockUdfps() /* requiresAod */
),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 95cd9eb..61832875 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -165,7 +165,7 @@
val QS_USER_DETAIL_SHORTCUT =
resourceBooleanFlag(
R.bool.flag_lockscreen_qs_user_detail_shortcut,
- "qs_user_detail_shortcut"
+ "qs_user_detail_shortcut",
)
// TODO(b/254512383): Tracking Bug
@@ -365,11 +365,6 @@
val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
releasedFlag("zj_285570694_lockscreen_transition_from_aod")
- // 3000 - dream
- // TODO(b/285059790) : Tracking Bug
- @JvmField
- val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
-
// TODO(b/283447257): Tracking bug
@JvmField
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
index 932e5af..cc77f68a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt
@@ -22,6 +22,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.time.SystemClock
+import com.google.android.msdl.domain.MSDLPlayer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -39,6 +40,7 @@
@JvmOverloads
constructor(
vibratorHelper: VibratorHelper,
+ msdlPlayer: MSDLPlayer,
systemClock: SystemClock,
sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
@@ -63,6 +65,7 @@
private val sliderHapticFeedbackProvider =
SliderHapticFeedbackProvider(
vibratorHelper,
+ msdlPlayer,
dragVelocityProvider,
sliderHapticFeedbackConfig,
systemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index 06428b7..bc4f531 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -22,7 +22,11 @@
import android.view.animation.AccelerateInterpolator
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.pow
@@ -38,6 +42,7 @@
*/
class SliderHapticFeedbackProvider(
private val vibratorHelper: VibratorHelper,
+ private val msdlPlayer: MSDLPlayer,
private val velocityProvider: SliderDragVelocityProvider,
private val config: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
private val clock: com.android.systemui.util.time.SystemClock,
@@ -67,11 +72,20 @@
*/
private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
val powerScale = scaleOnEdgeCollision(absoluteVelocity)
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
- .compose()
- vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+ if (Flags.msdlFeedback()) {
+ val properties =
+ InteractionProperties.DynamicVibrationScale(
+ powerScale,
+ VIBRATION_ATTRIBUTES_PIPELINING,
+ )
+ msdlPlayer.playToken(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT, properties)
+ } else {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
+ .compose()
+ vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+ }
}
/**
@@ -112,16 +126,26 @@
val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
- // Trigger the vibration composition
- val composition = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
- }
- vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+ // Deliver haptic feedback
+ performContinuousSliderDragVibration(powerScale)
dragTextureLastTime = currentTime
dragTextureLastProgress = normalizedSliderProgress
}
+ private fun performContinuousSliderDragVibration(scale: Float) {
+ if (Flags.msdlFeedback()) {
+ val properties =
+ InteractionProperties.DynamicVibrationScale(scale, VIBRATION_ATTRIBUTES_PIPELINING)
+ msdlPlayer.playToken(MSDLToken.DRAG_INDICATOR_CONTINUOUS, properties)
+ } else {
+ val composition = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
+ }
+ vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+ }
+ }
+
/**
* Get the scale of the drag texture vibration.
*
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
index 1dbcb3df..de24259 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.time.SystemClock
+import com.google.android.msdl.domain.MSDLPlayer
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -50,6 +51,7 @@
@Assisted private val sliderHapticFeedbackConfig: SliderHapticFeedbackConfig,
@Assisted private val sliderTrackerConfig: SeekableSliderTrackerConfig,
vibratorHelper: VibratorHelper,
+ msdlPlayer: MSDLPlayer,
systemClock: SystemClock,
) : ExclusiveActivatable() {
@@ -78,6 +80,7 @@
private val sliderHapticFeedbackProvider =
SliderHapticFeedbackProvider(
vibratorHelper,
+ msdlPlayer,
dragVelocityProvider,
sliderHapticFeedbackConfig,
systemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 3c8bb09..5cade68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,10 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.composable
-import android.content.Context
-import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Icon
-import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
@@ -55,12 +52,8 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
-import androidx.compose.material.icons.filled.Apps
import androidx.compose.material.icons.filled.ExpandMore
-import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.filled.Tv
-import androidx.compose.material.icons.filled.VerticalSplit
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
@@ -111,16 +104,15 @@
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.CentralSurfaces
@Composable
fun ShortcutHelper(
@@ -187,7 +179,7 @@
private fun ShortcutHelperSinglePane(
searchQuery: String,
onSearchQueryChanged: (String) -> Unit,
- categories: List<ShortcutCategory>,
+ categories: List<ShortcutCategoryUi>,
selectedCategoryType: ShortcutCategoryType?,
onCategorySelected: (ShortcutCategoryType?) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
@@ -228,7 +220,7 @@
@Composable
private fun CategoriesPanelSinglePane(
searchQuery: String,
- categories: List<ShortcutCategory>,
+ categories: List<ShortcutCategoryUi>,
selectedCategoryType: ShortcutCategoryType?,
onCategorySelected: (ShortcutCategoryType?) -> Unit,
) {
@@ -267,7 +259,7 @@
@Composable
private fun CategoryItemSinglePane(
searchQuery: String,
- category: ShortcutCategory,
+ category: ShortcutCategoryUi,
isExpanded: Boolean,
onClick: () -> Unit,
shape: Shape,
@@ -278,9 +270,9 @@
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp),
) {
- ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon)
+ ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource)
Spacer(modifier = Modifier.width(16.dp))
- Text(category.label(LocalContext.current))
+ Text(category.label)
Spacer(modifier = Modifier.weight(1f))
RotatingExpandCollapseIcon(isExpanded)
}
@@ -291,23 +283,6 @@
}
}
-private val ShortcutCategory.icon: IconSource
- @Composable
- get() =
- when (type) {
- ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
- ShortcutCategoryType.MultiTasking ->
- IconSource(imageVector = Icons.Default.VerticalSplit)
- ShortcutCategoryType.InputMethodEditor ->
- IconSource(imageVector = Icons.Default.Keyboard)
- ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
- is ShortcutCategoryType.CurrentApp -> {
- val context = LocalContext.current
- val iconDrawable = context.packageManager.getApplicationIcon(type.packageName)
- IconSource(painter = rememberDrawablePainter(drawable = iconDrawable))
- }
- }
-
@Composable
fun ShortcutCategoryIcon(
source: IconSource,
@@ -322,37 +297,6 @@
}
}
-private fun ShortcutCategory.label(context: Context): String =
- when (type) {
- ShortcutCategoryType.System -> context.getString(R.string.shortcut_helper_category_system)
- ShortcutCategoryType.MultiTasking ->
- context.getString(R.string.shortcut_helper_category_multitasking)
- ShortcutCategoryType.InputMethodEditor ->
- context.getString(R.string.shortcut_helper_category_input)
- ShortcutCategoryType.AppCategories ->
- context.getString(R.string.shortcut_helper_category_app_shortcuts)
- is ShortcutCategoryType.CurrentApp -> getApplicationLabelForCurrentApp(type, context)
- }
-
-private fun getApplicationLabelForCurrentApp(
- type: ShortcutCategoryType.CurrentApp,
- context: Context,
-): String {
- val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
- return try {
- val currentAppInfo =
- packageManagerForUser.getApplicationInfoAsUser(
- type.packageName,
- /* flags = */ 0,
- context.userId,
- )
- packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
- } catch (e: NameNotFoundException) {
- Log.wtf(ShortcutHelper.TAG, "Couldn't find app info by package name ${type.packageName}")
- context.getString(R.string.shortcut_helper_category_current_app_shortcuts)
- }
-}
-
@Composable
private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
val expandIconRotationDegrees by
@@ -384,7 +328,7 @@
}
@Composable
-private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategory) {
+private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) {
Column(Modifier.padding(horizontal = 16.dp)) {
category.subCategories.fastForEach { subCategory ->
ShortcutSubCategorySinglePane(searchQuery, subCategory)
@@ -409,7 +353,7 @@
searchQuery: String,
onSearchQueryChanged: (String) -> Unit,
modifier: Modifier = Modifier,
- categories: List<ShortcutCategory>,
+ categories: List<ShortcutCategoryUi>,
selectedCategoryType: ShortcutCategoryType?,
onCategorySelected: (ShortcutCategoryType?) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
@@ -434,7 +378,7 @@
}
@Composable
-private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
+private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?) {
val listState = rememberLazyListState()
LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
if (category == null) {
@@ -670,10 +614,10 @@
private fun StartSidePanel(
onSearchQueryChanged: (String) -> Unit,
modifier: Modifier,
- categories: List<ShortcutCategory>,
+ categories: List<ShortcutCategoryUi>,
onKeyboardSettingsClicked: () -> Unit,
selectedCategory: ShortcutCategoryType?,
- onCategoryClicked: (ShortcutCategory) -> Unit,
+ onCategoryClicked: (ShortcutCategoryUi) -> Unit,
) {
Column(modifier) {
ShortcutsSearchBar(onSearchQueryChanged)
@@ -690,15 +634,15 @@
@Composable
private fun CategoriesPanelTwoPane(
- categories: List<ShortcutCategory>,
+ categories: List<ShortcutCategoryUi>,
selectedCategory: ShortcutCategoryType?,
- onCategoryClicked: (ShortcutCategory) -> Unit,
+ onCategoryClicked: (ShortcutCategoryUi) -> Unit,
) {
Column {
categories.fastForEach {
CategoryItemTwoPane(
- label = it.label(LocalContext.current),
- iconSource = it.icon,
+ label = it.label,
+ iconSource = it.iconSource,
selected = selectedCategory == it.type,
onClick = { onCategoryClicked(it) },
)
@@ -833,7 +777,7 @@
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
- "Keyboard Settings",
+ stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
new file mode 100644
index 0000000..f5d478b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.keyboard.shortcut.ui.model
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+
+data class ShortcutCategoryUi(
+ val label: String,
+ val iconSource: IconSource,
+ val type: ShortcutCategoryType,
+ val subCategories: List<ShortcutSubCategory>,
+) {
+ constructor(
+ label: String,
+ iconSource: IconSource,
+ shortcutCategory: ShortcutCategory,
+ ) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index d2122b3..8f23261 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -16,14 +16,13 @@
package com.android.systemui.keyboard.shortcut.ui.model
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
sealed interface ShortcutsUiState {
data class Active(
val searchQuery: String,
- val shortcutCategories: List<ShortcutCategory>,
+ val shortcutCategories: List<ShortcutCategoryUi>,
val defaultSelectedCategory: ShortcutCategoryType?,
) : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 04aa04d..20d09ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -17,6 +17,15 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
import android.app.role.RoleManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.util.Log
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Android
+import androidx.compose.material.icons.filled.Apps
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
+import com.android.compose.ui.graphics.painter.DrawablePainter
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -25,7 +34,10 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -51,6 +63,7 @@
) {
private val searchQuery = MutableStateFlow("")
+ private val userContext = userTracker.createCurrentUserContext(userTracker.userContext)
val shouldShow =
categoriesInteractor.shortcutCategories
@@ -68,9 +81,10 @@
val categoriesWithLauncherExcluded = excludeLauncherApp(categories)
val filteredCategories =
filterCategoriesBySearchQuery(query, categoriesWithLauncherExcluded)
+ val shortcutCategoriesUi = convertCategoriesModelToUiModel(filteredCategories)
ShortcutsUiState.Active(
searchQuery = query,
- shortcutCategories = filteredCategories,
+ shortcutCategories = shortcutCategoriesUi,
defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
)
}
@@ -78,9 +92,73 @@
.stateIn(
scope = backgroundScope,
started = SharingStarted.Lazily,
- initialValue = ShortcutsUiState.Inactive
+ initialValue = ShortcutsUiState.Inactive,
)
+ private fun convertCategoriesModelToUiModel(
+ categories: List<ShortcutCategory>
+ ): List<ShortcutCategoryUi> {
+ return categories.map { category ->
+ ShortcutCategoryUi(
+ label = getShortcutCategoryLabel(category.type),
+ iconSource = getShortcutCategoryIcon(category.type),
+ shortcutCategory = category,
+ )
+ }
+ }
+
+ private fun getShortcutCategoryIcon(type: ShortcutCategoryType): IconSource {
+ return when (type) {
+ ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
+ ShortcutCategoryType.MultiTasking ->
+ IconSource(imageVector = Icons.Default.VerticalSplit)
+ ShortcutCategoryType.InputMethodEditor ->
+ IconSource(imageVector = Icons.Default.Keyboard)
+ ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
+ is CurrentApp -> {
+ try {
+ val iconDrawable =
+ userContext.packageManager.getApplicationIcon(type.packageName)
+ IconSource(painter = DrawablePainter(drawable = iconDrawable))
+ } catch (e: NameNotFoundException) {
+ Log.wtf(
+ "ShortcutHelperViewModel",
+ "Package not found when retrieving icon for ${type.packageName}",
+ )
+ IconSource(imageVector = Icons.Default.Android)
+ }
+ }
+ }
+ }
+
+ private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String =
+ when (type) {
+ ShortcutCategoryType.System ->
+ userContext.getString(R.string.shortcut_helper_category_system)
+ ShortcutCategoryType.MultiTasking ->
+ userContext.getString(R.string.shortcut_helper_category_multitasking)
+ ShortcutCategoryType.InputMethodEditor ->
+ userContext.getString(R.string.shortcut_helper_category_input)
+ ShortcutCategoryType.AppCategories ->
+ userContext.getString(R.string.shortcut_helper_category_app_shortcuts)
+ is CurrentApp -> getApplicationLabelForCurrentApp(type)
+ }
+
+ private fun getApplicationLabelForCurrentApp(type: CurrentApp): String {
+ try {
+ val packageManagerForUser = userContext.packageManager
+ val currentAppInfo =
+ packageManagerForUser.getApplicationInfo(type.packageName, /* flags= */ 0)
+ return packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
+ } catch (e: NameNotFoundException) {
+ Log.wtf(
+ "ShortcutHelperViewModel",
+ "Package Not found when retrieving Label for ${type.packageName}",
+ )
+ return "Current App"
+ }
+ }
+
private suspend fun excludeLauncherApp(
categories: List<ShortcutCategory>
): List<ShortcutCategory> {
@@ -111,7 +189,7 @@
private fun filterCategoriesBySearchQuery(
query: String,
- categories: List<ShortcutCategory>
+ categories: List<ShortcutCategory>,
): List<ShortcutCategory> {
val lowerCaseTrimmedQuery = query.trim().lowercase()
if (lowerCaseTrimmedQuery.isEmpty()) {
@@ -132,7 +210,7 @@
private fun filterSubCategoriesBySearchQuery(
subCategories: List<ShortcutSubCategory>,
- query: String
+ query: String,
) =
subCategories
.map { subCategory ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fbc76c5..60a306b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2975,7 +2975,7 @@
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
- if (DEBUG) Log.d(TAG, "keyguardGoingAway");
+ Log.d(TAG, "keyguardGoingAwayRunnable");
mKeyguardViewControllerLazy.get().keyguardGoingAway();
int flags = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 8210174..9e99a87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -114,7 +114,7 @@
"away' is isInTransitionToState(GONE), but consider using more specific flows " +
"whenever possible."
)
- val isKeyguardGoingAway: Flow<Boolean>
+ val isKeyguardGoingAway: MutableStateFlow<Boolean>
/**
* Whether the keyguard is enabled, per [KeyguardService]. If the keyguard is not enabled, the
@@ -184,9 +184,6 @@
/** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
val isDreamingWithOverlay: Flow<Boolean>
- /** Observable for device dreaming state and the active dream is hosted in lockscreen */
- val isActiveDreamLockscreenHosted: StateFlow<Boolean>
-
/**
* Observable for the amount of doze we are currently in.
*
@@ -308,8 +305,6 @@
fun setIsDozing(isDozing: Boolean)
- fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
-
fun dozeTimeTick()
fun showDismissibleKeyguard()
@@ -637,9 +632,6 @@
private val _isQuickSettingsVisible = MutableStateFlow(false)
override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
- private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
- override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
-
private val _shortcutAbsoluteTop = MutableStateFlow(0F)
override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
@@ -655,10 +647,6 @@
override fun onUnlockedChanged() {
isKeyguardDismissible.value = keyguardStateController.isUnlocked
}
-
- override fun onKeyguardGoingAwayChanged() {
- isKeyguardGoingAway.value = keyguardStateController.isKeyguardGoingAway
- }
}
keyguardStateController.addCallback(callback)
@@ -698,10 +686,6 @@
_isQuickSettingsVisible.value = isVisible
}
- override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
- _isActiveDreamLockscreenHosted.value = isLockscreenHosted
- }
-
override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
_clockShouldBeCentered.value = shouldBeCentered
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8c7fe5f..0c2d577 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -131,12 +131,13 @@
.collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+ val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
if (!deviceEntryInteractor.isLockscreenEnabled()) {
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GONE)
}
- } else if (canDismissLockscreen()) {
+ } else if (canDismissLockscreen() || isKeyguardGoingAway) {
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 5b7eedd..d18d6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -105,21 +105,13 @@
combine(
shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
- keyguardInteractor.isActiveDreamLockscreenHosted,
isOnAod,
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
keyguardInteractor.isDozing,
- ) {
- isShadeLayoutWide,
- areAnyNotificationsPresent,
- isActiveDreamLockscreenHosted,
- isOnAod,
- isHeadsUp,
- isDozing ->
+ ) { isShadeLayoutWide, areAnyNotificationsPresent, isOnAod, isHeadsUp, isDozing ->
when {
!isShadeLayoutWide -> true
!areAnyNotificationsPresent -> true
- isActiveDreamLockscreenHosted -> true
// Pulsing notification appears on the right. Move clock left to avoid overlap.
isHeadsUp && isDozing -> false
else -> isOnAod
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6ecbc61..0d5ad54 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -189,9 +189,6 @@
/** Whether any dreaming is running, including the doze dream. */
val isDreamingAny: Flow<Boolean> = repository.isDreaming
- /** Whether the system is dreaming and the active dream is hosted in lockscreen */
- val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
-
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
@@ -244,7 +241,7 @@
/** Whether the keyguard is going away. */
@Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
- val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+ val isKeyguardGoingAway: StateFlow<Boolean> = repository.isKeyguardGoingAway.asStateFlow()
/** Keyguard can be clipped at the top as the shade is dragged */
val topClippingBounds: Flow<Int?> by lazy {
@@ -477,10 +474,6 @@
}
}
- fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
- repository.setIsActiveDreamLockscreenHosted(isLockscreenHosted)
- }
-
/** Sets whether quick settings or quick-quick settings is visible. */
fun setQuickSettingsVisible(isVisible: Boolean) {
repository.setQuickSettingsVisible(isVisible)
@@ -549,6 +542,10 @@
repository.setShortcutAbsoluteTop(top)
}
+ fun setIsKeyguardGoingAway(isGoingAway: Boolean) {
+ repository.isKeyguardGoingAway.value = isGoingAway
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index fcf486b..d4d7e75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -20,6 +20,7 @@
import android.media.AudioManager
import android.view.KeyEvent
import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction
import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
@@ -45,6 +46,7 @@
private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
private val backActionInteractor: BackActionInteractor,
private val powerInteractor: PowerInteractor,
+ private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
) {
fun dispatchKeyEvent(event: KeyEvent): Boolean {
@@ -96,8 +98,15 @@
}
fun interceptMediaKey(event: KeyEvent): Boolean {
- return statusBarStateController.state == StatusBarState.KEYGUARD &&
- statusBarKeyguardViewManager.interceptMediaKey(event)
+ return when (statusBarStateController.state) {
+ StatusBarState.KEYGUARD ->
+ if (ComposeBouncerFlags.isEnabled) {
+ keyguardMediaKeyInteractor.processMediaKeyEvent(event)
+ } else {
+ statusBarKeyguardViewManager.interceptMediaKey(event)
+ }
+ else -> false
+ }
}
private fun dispatchMenuKeyEvent(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt
new file mode 100644
index 0000000..1404ef6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractor.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import javax.inject.Inject
+
+/** Handle media key events while on keyguard or bouncer. */
+@SysUISingleton
+class KeyguardMediaKeyInteractor
+@Inject
+constructor(
+ private val telephonyInteractor: TelephonyInteractor,
+ private val audioRepository: AudioRepository,
+) : ExclusiveActivatable() {
+
+ /**
+ * Allows the media keys to work when the keyguard is showing. Forwards the relevant media keys
+ * to [AudioManager].
+ *
+ * @param event The key event
+ * @return whether the event was consumed as a media key.
+ */
+ fun processMediaKeyEvent(event: KeyEvent): Boolean {
+ if (ComposeBouncerFlags.isUnexpectedlyInLegacyMode()) {
+ return false
+ }
+ val keyCode = event.keyCode
+ if (event.action == KeyEvent.ACTION_DOWN) {
+ when (keyCode) {
+ KeyEvent.KEYCODE_MEDIA_PLAY,
+ KeyEvent.KEYCODE_MEDIA_PAUSE,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
+ /* Suppress PLAY/PAUSE toggle when phone is ringing or
+ * in-call to avoid music playback */
+ // suppress key event
+ return telephonyInteractor.isInCall.value
+ }
+
+ KeyEvent.KEYCODE_MUTE,
+ KeyEvent.KEYCODE_HEADSETHOOK,
+ KeyEvent.KEYCODE_MEDIA_STOP,
+ KeyEvent.KEYCODE_MEDIA_NEXT,
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+ KeyEvent.KEYCODE_MEDIA_REWIND,
+ KeyEvent.KEYCODE_MEDIA_RECORD,
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+ KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK -> {
+ audioRepository.dispatchMediaKeyEvent(event)
+ return true
+ }
+
+ KeyEvent.KEYCODE_VOLUME_UP,
+ KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_MUTE -> return false
+ }
+ } else if (event.action == KeyEvent.ACTION_UP) {
+ when (keyCode) {
+ KeyEvent.KEYCODE_MUTE,
+ KeyEvent.KEYCODE_HEADSETHOOK,
+ KeyEvent.KEYCODE_MEDIA_PLAY,
+ KeyEvent.KEYCODE_MEDIA_PAUSE,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+ KeyEvent.KEYCODE_MEDIA_STOP,
+ KeyEvent.KEYCODE_MEDIA_NEXT,
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+ KeyEvent.KEYCODE_MEDIA_REWIND,
+ KeyEvent.KEYCODE_MEDIA_RECORD,
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+ KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK -> {
+ audioRepository.dispatchMediaKeyEvent(event)
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ override suspend fun onActivated(): Nothing {
+ // Collect to keep this flow hot for this interactor.
+ telephonyInteractor.isInCall.collect {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index cf747c8..34173a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
@@ -96,16 +97,19 @@
/** Limit the max alpha for the scrim to allow for some transparency */
val maxAlpha: Flow<Float> =
- transitionInteractor
- .isInTransition(
- edge = Edge.create(Scenes.Gone, KeyguardState.AOD),
- edgeWithoutSceneContainer = Edge.create(KeyguardState.GONE, KeyguardState.AOD),
+ anyOf(
+ transitionInteractor.isInTransition(
+ edge = Edge.create(Scenes.Gone, KeyguardState.AOD),
+ edgeWithoutSceneContainer = Edge.create(KeyguardState.GONE, KeyguardState.AOD),
+ ),
+ transitionInteractor.isInTransition(
+ Edge.create(KeyguardState.OCCLUDED, KeyguardState.AOD)
+ ),
)
.flatMapLatest { isInTransition ->
- // During GONE->AOD transitions, the home screen and wallpaper are still visible
- // until
- // WM is told to hide them, which occurs at the end of the animation. Use an opaque
- // scrim until this transition is complete
+ // During transitions like GONE->AOD, surfaces like the launcher may be visible
+ // until WM is told to hide them, which occurs at the end of the animation. Use an
+ // opaque scrim until this transition is complete.
if (isInTransition) {
flowOf(1f)
} else {
@@ -149,7 +153,6 @@
KeyguardState.DOZING -> false
KeyguardState.AOD -> false
KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
KeyguardState.GLANCEABLE_HUB -> true
KeyguardState.ALTERNATE_BOUNCER -> true
KeyguardState.PRIMARY_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 080ddfd..f0e79b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -41,12 +41,6 @@
*/
DREAMING,
/**
- * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
- * It is a special version of DREAMING state but not DOZING. The active dream will be windowless
- * and hosted in the lockscreen.
- */
- DREAMING_LOCKSCREEN_HOSTED,
- /**
* The device has entered a special low-power mode within SystemUI, also called the Always-on
* Display (AOD). A minimal UI is presented to show critical information. If the device is in
* low-power mode without a UI, then it is DOZING.
@@ -125,7 +119,6 @@
OFF,
DOZING,
DREAMING,
- DREAMING_LOCKSCREEN_HOSTED,
AOD,
ALTERNATE_BOUNCER,
OCCLUDED,
@@ -142,7 +135,6 @@
OFF,
DOZING,
DREAMING,
- DREAMING_LOCKSCREEN_HOSTED,
AOD,
ALTERNATE_BOUNCER,
OCCLUDED,
@@ -166,7 +158,6 @@
OFF -> false
DOZING -> false
DREAMING -> false
- DREAMING_LOCKSCREEN_HOSTED -> false
GLANCEABLE_HUB -> true
AOD -> false
ALTERNATE_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
index 32757ce..741cc02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -19,6 +19,7 @@
import android.animation.ValueAnimator
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators.ALPHA_IN
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -43,14 +44,24 @@
}
}
launch("$TAG#viewModel.maxAlpha") {
- viewModel.maxAlpha.collect { alpha ->
+ var animator: ValueAnimator? = null
+ viewModel.maxAlpha.collect { (alpha, animate) ->
if (alpha != revealScrim.alpha) {
- ValueAnimator.ofFloat(revealScrim.alpha, alpha).apply {
- duration = 400
- addUpdateListener { animation ->
- revealScrim.alpha = animation.getAnimatedValue() as Float
- }
- start()
+ animator?.cancel()
+ if (!animate) {
+ revealScrim.alpha = alpha
+ } else {
+ animator =
+ ValueAnimator.ofFloat(revealScrim.alpha, alpha).apply {
+ startDelay = 333
+ duration = 733
+ interpolator = ALPHA_IN
+ addUpdateListener { animation ->
+ revealScrim.alpha =
+ animation.getAnimatedValue() as Float
+ }
+ start()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 68244d8..4c667c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -114,7 +114,6 @@
keyguardTransitionInteractor.currentKeyguardState.replayCache.last()
) {
KeyguardState.GLANCEABLE_HUB,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
KeyguardState.GONE,
KeyguardState.OCCLUDED,
KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index d3bb4f5..f5e0c81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -95,11 +95,10 @@
.shareIn(scope, SharingStarted.WhileSubscribed())
.onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) }
private val alphaMultiplierFromShadeExpansion: Flow<Float> =
- combine(
- showingAlternateBouncer,
+ combine(showingAlternateBouncer, shadeExpansion, qsProgress) {
+ showingAltBouncer,
shadeExpansion,
- qsProgress,
- ) { showingAltBouncer, shadeExpansion, qsProgress ->
+ qsProgress ->
val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
if (showingAltBouncer) {
1f
@@ -113,13 +112,9 @@
combine(
burnInInteractor.deviceEntryIconXOffset,
burnInInteractor.deviceEntryIconYOffset,
- burnInInteractor.udfpsProgress
+ burnInInteractor.udfpsProgress,
) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
- BurnInOffsets(
- fullyDozingBurnInX,
- fullyDozingBurnInY,
- fullyDozingBurnInProgress,
- )
+ BurnInOffsets(fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress)
}
private val dozeAmount: Flow<Float> = transitionInteractor.transitionValue(KeyguardState.AOD)
@@ -129,22 +124,15 @@
BurnInOffsets(
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
- floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress)
+ floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress),
)
}
val deviceEntryViewAlpha: Flow<Float> =
- combine(
- transitionAlpha,
- alphaMultiplierFromShadeExpansion,
- ) { alpha, alphaMultiplier ->
+ combine(transitionAlpha, alphaMultiplierFromShadeExpansion) { alpha, alphaMultiplier ->
alpha * alphaMultiplier
}
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = 0f,
- )
+ .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float {
return when (keyguardState) {
@@ -155,11 +143,10 @@
KeyguardState.GLANCEABLE_HUB,
KeyguardState.GONE,
KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.UNDEFINED, -> 0f
+ KeyguardState.UNDEFINED -> 0f
KeyguardState.AOD,
KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.LOCKSCREEN, -> 1f
+ KeyguardState.LOCKSCREEN -> 1f
}
}
@@ -171,7 +158,7 @@
combine(
transitionInteractor.startedKeyguardTransitionStep.sample(
shadeInteractor.isAnyFullyExpanded,
- ::Pair
+ ::Pair,
),
animatedBurnInOffsets,
nonAnimatedBurnInOffsets,
@@ -228,10 +215,9 @@
}
val iconType: Flow<DeviceEntryIconView.IconType> =
- combine(
- deviceEntryUdfpsInteractor.isListeningForUdfps,
- isUnlocked,
- ) { isListeningForUdfps, isUnlocked ->
+ combine(deviceEntryUdfpsInteractor.isListeningForUdfps, isUnlocked) {
+ isListeningForUdfps,
+ isUnlocked ->
if (isListeningForUdfps) {
if (isUnlocked) {
// Don't show any UI until isUnlocked=false. This covers the case
@@ -250,10 +236,7 @@
val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
private val isInteractive: Flow<Boolean> =
- combine(
- iconType,
- isUdfpsSupported,
- ) { deviceEntryStatus, isUdfps ->
+ combine(iconType, isUdfpsSupported) { deviceEntryStatus, isUdfps ->
when (deviceEntryStatus) {
DeviceEntryIconView.IconType.LOCK -> isUdfps
DeviceEntryIconView.IconType.UNLOCK -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
index af6cd16..6d1aefe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -21,6 +21,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/**
* Models UI state for the light reveal scrim, which is used during screen on and off animations to
@@ -32,7 +33,16 @@
constructor(private val interactor: LightRevealScrimInteractor) {
val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
val revealAmount: Flow<Float> = interactor.revealAmount
- val maxAlpha: Flow<Float> = interactor.maxAlpha
+
+ /** Max alpha for the scrim + whether to animate the change */
+ val maxAlpha: Flow<Pair<Float, Boolean>> =
+ interactor.maxAlpha.map { alpha ->
+ Pair(
+ alpha,
+ // Darken immediately if going to be fully opaque
+ if (alpha == 1f) false else true,
+ )
+ }
fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) {
interactor.setWallpaperSupportsAmbientMode(supportsAmbientMode)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 6db91ac..4071b13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -221,7 +221,7 @@
{ notificationScrimClippingParams.params.top },
// Only allow scrolling when we are fully expanded. That way, we don't intercept
// swipes in lockscreen (when somehow QS is receiving touches).
- { scrollState.canScrollForward && viewModel.isQsFullyExpanded },
+ { (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
)
frame.addView(
composeView,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 71fa0ac..7b25939 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -77,25 +77,24 @@
colors: TileColors,
squishiness: () -> Float,
accessibilityUiState: AccessibilityUiState? = null,
- toggleClickSupported: Boolean = false,
iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
- onClick: () -> Unit = {},
- onLongClick: () -> Unit = {},
+ toggleClick: (() -> Unit)? = null,
+ onLongClick: (() -> Unit)? = null,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = tileHorizontalArrangement(),
) {
// Icon
- val longPressLabel = longPressLabel()
+ val longPressLabel = longPressLabel().takeIf { onLongClick != null }
Box(
modifier =
- Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
+ Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
Modifier.clip(iconShape)
.verticalSquish(squishiness)
.background(colors.iconBackground, { 1f })
.combinedClickable(
- onClick = onClick,
+ onClick = toggleClick!!,
onLongClick = onLongClick,
onLongClickLabel = longPressLabel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index d2ec958..b581c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -202,14 +202,17 @@
topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
) { innerPadding ->
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ val scrollState = rememberScrollState()
+ LaunchedEffect(listState.dragInProgress) {
+ if (listState.dragInProgress) {
+ scrollState.animateScrollTo(0)
+ }
+ }
+
Column(
verticalArrangement =
spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier =
- modifier
- .fillMaxSize()
- .verticalScroll(rememberScrollState())
- .padding(innerPadding),
+ modifier = modifier.fillMaxSize().verticalScroll(scrollState).padding(innerPadding),
) {
AnimatedContent(
targetState = listState.dragInProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 52d5261..5f28fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -160,19 +160,18 @@
)
} else {
val iconShape = TileDefaults.animateIconShape(uiState.state)
+ val secondaryClick: (() -> Unit)? =
+ { tile.onSecondaryClick() }.takeIf { uiState.handlesSecondaryClick }
+ val longClick: (() -> Unit)? =
+ { tile.onLongClick(expandable) }.takeIf { uiState.handlesLongClick }
LargeTileContent(
label = uiState.label,
secondaryLabel = uiState.secondaryLabel,
icon = icon,
colors = colors,
iconShape = iconShape,
- toggleClickSupported = state.handlesSecondaryClick,
- onClick = {
- if (state.handlesSecondaryClick) {
- tile.onSecondaryClick()
- }
- },
- onLongClick = { tile.onLongClick(expandable) },
+ toggleClick = secondaryClick,
+ onLongClick = longClick,
accessibilityUiState = uiState.accessibilityUiState,
squishiness = squishiness,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index aa42080..56675e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -33,6 +33,7 @@
val label: String,
val secondaryLabel: String,
val state: Int,
+ val handlesLongClick: Boolean,
val handlesSecondaryClick: Boolean,
val icon: Supplier<QSTile.Icon?>,
val accessibilityUiState: AccessibilityUiState,
@@ -86,6 +87,7 @@
label = label?.toString() ?: "",
secondaryLabel = secondaryLabel?.toString() ?: "",
state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state,
+ handlesLongClick = handlesLongClick,
handlesSecondaryClick = handlesSecondaryClick,
icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
AccessibilityUiState(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
deleted file mode 100644
index f73d204..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.annotation.UserIdInt
-import android.content.ComponentName
-import android.graphics.Rect
-import android.os.UserHandle
-import android.view.Display
-
-/**
- * Provides policy decision-making information to screenshot request handling.
- */
-interface ScreenshotPolicy {
-
- /** @return true if the user is a managed profile (a.k.a. work profile) */
- suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean
-
- /**
- * Requests information about the owner of display content which occupies a majority of the
- * screenshot and/or has most recently been interacted with at the time the screenshot was
- * requested.
- *
- * @param displayId the id of the display to inspect
- * @return content info for the primary content on the display
- */
- suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo
-
- data class DisplayContentInfo(
- val component: ComponentName,
- val bounds: Rect,
- val user: UserHandle,
- val taskId: Int,
- )
-
- fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
deleted file mode 100644
index 21a7310..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.annotation.UserIdInt
-import android.app.ActivityTaskManager
-import android.app.ActivityTaskManager.RootTaskInfo
-import android.app.IActivityTaskManager
-import android.app.WindowConfiguration
-import android.app.WindowConfiguration.activityTypeToString
-import android.app.WindowConfiguration.windowingModeToString
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.Rect
-import android.os.Process
-import android.os.RemoteException
-import android.os.UserHandle
-import android.os.UserManager
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.infra.ServiceConnector
-import com.android.systemui.SystemUIService
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import java.util.Arrays
-import javax.inject.Inject
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-
-@SysUISingleton
-internal open class ScreenshotPolicyImpl @Inject constructor(
- context: Context,
- private val userMgr: UserManager,
- private val atmService: IActivityTaskManager,
- @Background val bgDispatcher: CoroutineDispatcher,
- private val displayTracker: DisplayTracker
-) : ScreenshotPolicy {
-
- private val proxyConnector: ServiceConnector<IScreenshotProxy> =
- ServiceConnector.Impl(
- context,
- Intent(context, ScreenshotProxyService::class.java),
- Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
- context.userId,
- IScreenshotProxy.Stub::asInterface
- )
-
- override fun getDefaultDisplayId(): Int {
- return displayTracker.defaultDisplayId
- }
-
- override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
- val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
- Log.d(TAG, "isManagedProfile: $managed")
- return managed
- }
-
- private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
- if (DEBUG) {
- debugLogRootTaskInfo(info)
- }
- return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
- info.isVisible &&
- info.isRunning &&
- info.numActivities > 0 &&
- info.topActivity != null &&
- info.childTaskIds.isNotEmpty()
- }
-
- /**
- * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
- * display. If no task is visible or the top task is covered by a system window, the info
- * reported will reference a SystemUI component instead.
- */
- override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
- // Determine if the notification shade is expanded. If so, task windows are not
- // visible behind it, so the screenshot should instead be associated with SystemUI.
- if (isNotificationShadeExpanded()) {
- return systemUiContent
- }
-
- val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
-
- // If no visible task is located, then report SystemUI as the foreground content
- val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
- return target.toDisplayContentInfo()
- }
-
- private fun debugLogRootTaskInfo(info: RootTaskInfo) {
- Log.d(TAG, "RootTaskInfo={" +
- "taskId=${info.taskId} " +
- "parentTaskId=${info.parentTaskId} " +
- "position=${info.position} " +
- "positionInParent=${info.positionInParent} " +
- "isVisible=${info.isVisible()} " +
- "visible=${info.visible} " +
- "isFocused=${info.isFocused} " +
- "isSleeping=${info.isSleeping} " +
- "isRunning=${info.isRunning} " +
- "windowMode=${windowingModeToString(info.windowingMode)} " +
- "activityType=${activityTypeToString(info.activityType)} " +
- "topActivity=${info.topActivity} " +
- "topActivityInfo=${info.topActivityInfo} " +
- "numActivities=${info.numActivities} " +
- "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
- "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
- "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
- "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
- "}"
- )
-
- for (j in 0 until info.childTaskIds.size) {
- Log.d(TAG, " *** [$j] ******")
- Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}")
- Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
- Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}")
- Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}")
- }
- }
-
- @VisibleForTesting
- open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
- withContext(bgDispatcher) {
- try {
- atmService.getAllRootTaskInfosOnDisplay(displayId)
- } catch (e: RemoteException) {
- Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
- listOf()
- }
- }
-
- @VisibleForTesting
- open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
- proxyConnector
- .postForResult { it.isNotificationShadeExpanded }
- .whenComplete { expanded, error ->
- if (error != null) {
- Log.e(TAG, "isNotificationShadeExpanded", error)
- }
- k.resume(expanded ?: false)
- }
- }
-
- @VisibleForTesting
- internal val systemUiContent =
- DisplayContentInfo(
- ComponentName(context, SystemUIService::class.java),
- Rect(),
- Process.myUserHandle(),
- ActivityTaskManager.INVALID_TASK_ID
- )
-}
-
-private const val TAG: String = "ScreenshotPolicyImpl"
-private const val DEBUG: Boolean = false
-
-@VisibleForTesting
-internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
- val topActivity: ComponentName = topActivity ?: error("should not be null")
- val topChildTask = childTaskIds.size - 1
- val childTaskId = childTaskIds[topChildTask]
- val childTaskUserId = childTaskUserIds[topChildTask]
- val childTaskBounds = childTaskBounds[topChildTask]
-
- return DisplayContentInfo(
- topActivity,
- childTaskBounds,
- UserHandle.of(childTaskUserId),
- childTaskId)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 254dde4..90695fa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -27,8 +27,6 @@
import com.android.systemui.screenshot.InteractiveScreenshotHandler;
import com.android.systemui.screenshot.LegacyScreenshotController;
import com.android.systemui.screenshot.ScreenshotController;
-import com.android.systemui.screenshot.ScreenshotPolicy;
-import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
@@ -66,9 +64,6 @@
TakeScreenshotExecutorImpl impl);
@Binds
- abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
-
- @Binds
abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
index dae8512..258befe 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
@@ -18,15 +18,19 @@
import android.content.Context
-/**
- * Implemented by [UserTrackerImpl].
- */
+/** Implemented by [UserTrackerImpl]. */
interface UserContextProvider {
+ /**
+ * provides system context, not current user context.
+ *
+ * To get current user context use [createCurrentUserContext] passing [userContext] as context
+ */
val userContext: Context
/**
* Creates the {@code context} with the current user.
+ *
* @see Context#createContextAsUser(UserHandle, int)
*/
fun createCurrentUserContext(context: Context): Context
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 75165cb..8703f68 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -40,6 +40,8 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.SystemClock;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import javax.inject.Inject;
/**
@@ -283,12 +285,14 @@
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
private final ActivityStarter mActivityStarter;
+ private final MSDLPlayer mMSDLPlayer;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
+ MSDLPlayer msdlPlayer,
SystemClock clock,
ActivityStarter activityStarter
) {
@@ -297,6 +301,7 @@
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
mActivityStarter = activityStarter;
+ mMSDLPlayer = msdlPlayer;
}
/**
@@ -314,6 +319,7 @@
.inflate(layout, viewRoot, false);
SeekbarHapticPlugin plugin = new SeekbarHapticPlugin(
mVibratorHelper,
+ mMSDLPlayer,
mSystemClock);
HapticSliderViewBinder.bind(viewRoot, plugin);
return new BrightnessSliderController(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
deleted file mode 100644
index 45fc68a..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 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.shade
-
-import android.os.PowerManager
-import android.view.GestureDetector
-import android.view.MotionEvent
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.statusbar.StatusBarState
-import javax.inject.Inject
-
-/**
- * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
- * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
- *
- * Touches go through the [NotificationShadeWindowViewController].
- */
-@SysUISingleton
-class LockscreenHostedDreamGestureListener
-@Inject
-constructor(
- private val falsingManager: FalsingManager,
- private val powerInteractor: PowerInteractor,
- private val statusBarStateController: StatusBarStateController,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
- private val keyguardRepository: KeyguardRepository,
- private val shadeLogger: ShadeLogger,
-) : GestureDetector.SimpleOnGestureListener() {
- private val TAG = this::class.simpleName
-
- override fun onSingleTapUp(e: MotionEvent): Boolean {
- if (shouldHandleMotionEvent()) {
- if (!falsingManager.isFalseTap(LOW_PENALTY)) {
- shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
- powerInteractor.wakeUpIfDreaming(
- "DREAMING_SINGLE_TAP",
- PowerManager.WAKE_REASON_TAP
- )
- } else {
- shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
- }
- return true
- }
- return false
- }
-
- private fun shouldHandleMotionEvent(): Boolean {
- return keyguardRepository.isActiveDreamLockscreenHosted.value &&
- statusBarStateController.state == StatusBarState.KEYGUARD &&
- !primaryBouncerInteractor.isBouncerShowing()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt
new file mode 100644
index 0000000..111d335
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAware.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.shade
+
+import javax.inject.Qualifier
+
+/**
+ * Qualifies classes that provide display-specific info for shade window components.
+ *
+ * The Shade window can be moved between displays with different characteristics (e.g., density,
+ * size). This annotation ensures that components within the shade window use the correct context
+ * and resources for the display they are currently on.
+ *
+ * Classes annotated with `@ShadeDisplayAware` (e.g., 'Context`, `Resources`, `LayoutInflater`,
+ * `ConfigurationController`) will be dynamically updated to reflect the current display's
+ * configuration. This ensures consistent rendering even when the shade window is moved to an
+ * external display.
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f99d8f1..520cbf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -214,7 +214,7 @@
private boolean mEnableBatteryDefender;
private boolean mIncompatibleCharger;
private int mChargingWattage;
- private int mBatteryLevel;
+ private int mBatteryLevel = -1;
private boolean mBatteryPresent = true;
protected long mChargingTimeRemaining;
private Pair<String, BiometricSourceType> mBiometricErrorMessageToShowOnScreenOn;
@@ -1032,12 +1032,16 @@
} else if (!TextUtils.isEmpty(mTransientIndication)) {
newIndication = mTransientIndication;
} else if (!mBatteryPresent) {
- // If there is no battery detected, hide the indication and bail
+ // If there is no battery detected, hide the indication area and bail
mIndicationArea.setVisibility(GONE);
return;
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
useMisalignmentColor = true;
newIndication = mAlignmentIndication;
+ } else if (mBatteryLevel == -1) {
+ // If the battery level is not initialized, hide the indication area
+ mIndicationArea.setVisibility(GONE);
+ return;
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
newIndication = computePowerIndication();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 2930de2..0eef8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -44,8 +44,12 @@
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
+import dagger.Module
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -63,26 +67,57 @@
* NOTE: any operation that modifies views directly must run on the provided executor, because these
* views are owned by ScreenDecorations and it runs in its own thread
*/
-@SysUISingleton
-open class PrivacyDotViewController
-@Inject
+interface PrivacyDotViewController {
+
+ // Only can be modified on @UiThread
+ var currentViewState: ViewState
+
+ var showingListener: ShowingListener?
+
+ fun setUiExecutor(e: DelayableExecutor)
+
+ fun getUiExecutor(): DelayableExecutor?
+
+ @UiThread fun setNewRotation(rot: Int)
+
+ @UiThread fun hideDotView(dot: View, animate: Boolean)
+
+ @UiThread fun showDotView(dot: View, animate: Boolean)
+
+ // Update the gravity and margins of the privacy views
+ @UiThread fun updateRotations(rotation: Int, paddingTop: Int)
+
+ @UiThread fun setCornerSizes(state: ViewState)
+
+ fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View)
+
+ @UiThread fun updateDotView(state: ViewState)
+
+ interface ShowingListener {
+ fun onPrivacyDotShown(v: View?)
+
+ fun onPrivacyDotHidden(v: View?)
+ }
+}
+
+open class PrivacyDotViewControllerImpl
+@AssistedInject
constructor(
@Main private val mainExecutor: Executor,
- @Application scope: CoroutineScope,
+ @Assisted scope: CoroutineScope,
private val stateController: StatusBarStateController,
- private val configurationController: ConfigurationController,
- private val contentInsetsProvider: StatusBarContentInsetsProvider,
+ @Assisted private val configurationController: ConfigurationController,
+ @Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider,
private val animationScheduler: SystemStatusAnimationScheduler,
- shadeInteractor: ShadeInteractor?
-) {
+ shadeInteractor: ShadeInteractor?,
+) : PrivacyDotViewController {
private lateinit var tl: View
private lateinit var tr: View
private lateinit var bl: View
private lateinit var br: View
// Only can be modified on @UiThread
- var currentViewState: ViewState = ViewState()
- get() = field
+ override var currentViewState: ViewState = ViewState()
@GuardedBy("lock")
private var nextViewState: ViewState = currentViewState.copy()
@@ -100,11 +135,7 @@
private val views: Sequence<View>
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
- var showingListener: ShowingListener? = null
- set(value) {
- field = value
- }
- get() = field
+ override var showingListener: PrivacyDotViewController.ShowingListener? = null
init {
contentInsetsProvider.addCallback(
@@ -153,16 +184,16 @@
}
}
- fun setUiExecutor(e: DelayableExecutor) {
+ override fun setUiExecutor(e: DelayableExecutor) {
uiExecutor = e
}
- fun getUiExecutor(): DelayableExecutor? {
+ override fun getUiExecutor(): DelayableExecutor? {
return uiExecutor
}
@UiThread
- fun setNewRotation(rot: Int) {
+ override fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
val isRtl: Boolean
@@ -187,13 +218,13 @@
rotation = rot,
paddingTop = paddingTop,
designatedCorner = newCorner,
- cornerIndex = index
+ cornerIndex = index,
)
}
}
@UiThread
- fun hideDotView(dot: View, animate: Boolean) {
+ override fun hideDotView(dot: View, animate: Boolean) {
dot.clearAnimation()
if (animate) {
dot.animate()
@@ -212,7 +243,7 @@
}
@UiThread
- fun showDotView(dot: View, animate: Boolean) {
+ override fun showDotView(dot: View, animate: Boolean) {
dot.clearAnimation()
if (animate) {
dot.visibility = View.VISIBLE
@@ -229,9 +260,8 @@
showingListener?.onPrivacyDotShown(dot)
}
- // Update the gravity and margins of the privacy views
@UiThread
- open fun updateRotations(rotation: Int, paddingTop: Int) {
+ override fun updateRotations(rotation: Int, paddingTop: Int) {
// To keep a view in the corner, its gravity is always the description of its current corner
// Therefore, just figure out which view is in which corner. This turns out to be something
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -262,7 +292,7 @@
}
@UiThread
- open fun setCornerSizes(state: ViewState) {
+ override fun setCornerSizes(state: ViewState) {
// StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
// in every rotation. The only thing we need to check is rtl
val rtl = state.layoutRtl
@@ -415,7 +445,7 @@
}
}
- fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+ override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
if (
this::tl.isInitialized &&
this::tr.isInitialized &&
@@ -457,7 +487,7 @@
landscapeRect = right,
upsideDownRect = bottom,
paddingTop = paddingTop,
- layoutRtl = rtl
+ layoutRtl = rtl,
)
}
}
@@ -533,7 +563,7 @@
}
@UiThread
- open fun updateDotView(state: ViewState) {
+ override fun updateDotView(state: ViewState) {
val shouldShow = state.shouldShowDot()
if (shouldShow != currentViewState.shouldShowDot()) {
if (shouldShow && state.designatedCorner != null) {
@@ -553,7 +583,7 @@
nextViewState =
nextViewState.copy(
systemPrivacyEventIsActive = true,
- contentDescription = contentDescr
+ contentDescription = contentDescr,
)
}
@@ -595,15 +625,18 @@
seascapeRect = rects[0],
portraitRect = rects[1],
landscapeRect = rects[2],
- upsideDownRect = rects[3]
+ upsideDownRect = rects[3],
)
}
}
- interface ShowingListener {
- fun onPrivacyDotShown(v: View?)
-
- fun onPrivacyDotHidden(v: View?)
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ scope: CoroutineScope,
+ configurationController: ConfigurationController,
+ contentInsetsProvider: StatusBarContentInsetsProvider,
+ ): PrivacyDotViewControllerImpl
}
}
@@ -662,7 +695,7 @@
val paddingTop: Int = 0,
val cornerIndex: Int = -1,
val designatedCorner: View? = null,
- val contentDescription: String? = null
+ val contentDescription: String? = null,
) {
fun shouldShowDot(): Boolean {
return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded
@@ -687,3 +720,18 @@
}
}
}
+
+@Module
+object PrivacyDotViewControllerModule {
+
+ @Provides
+ @SysUISingleton
+ fun controller(
+ factory: PrivacyDotViewControllerImpl.Factory,
+ @Application scope: CoroutineScope,
+ configurationController: ConfigurationController,
+ contentInsetsProvider: StatusBarContentInsetsProvider,
+ ): PrivacyDotViewController {
+ return factory.create(scope, configurationController, contentInsetsProvider)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
index 231a0b0..9fe4a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
@@ -32,13 +32,13 @@
interface NotificationActivityStarter {
/** Called when the user clicks on the notification bubble icon. */
- fun onNotificationBubbleIconClicked(entry: NotificationEntry?)
+ fun onNotificationBubbleIconClicked(entry: NotificationEntry)
/** Called when the user clicks on the surface of a notification. */
- fun onNotificationClicked(entry: NotificationEntry?, row: ExpandableNotificationRow?)
+ fun onNotificationClicked(entry: NotificationEntry, row: ExpandableNotificationRow)
/** Called when the user clicks on a button in the notification guts which fires an intent. */
- fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)
+ fun startNotificationGutsIntent(intent: Intent, appUid: Int, row: ExpandableNotificationRow)
/**
* Called when the user clicks "Manage" or "History" in the Shade. Prefer using
@@ -56,7 +56,7 @@
fun startSettingsIntent(view: View, intentInfo: SettingsIntent)
/** Called when the user succeed to drop notification to proper target view. */
- fun onDragSuccess(entry: NotificationEntry?)
+ fun onDragSuccess(entry: NotificationEntry)
val isCollapsingToShowActivityOverLockscreen: Boolean
get() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 2ee1dffd..9580016 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -35,6 +35,9 @@
* This cache is safe for multithreaded usage, and is recommended for objects that take a while to
* resolve (such as drawables, or things that require binder calls). As such, [getOrFetch] is
* recommended to be run on a background thread, while [purge] can be done from any thread.
+ *
+ * Important: This cache does NOT have a maximum size, cleaning it up (via [purge]) is the
+ * responsibility of the caller, to avoid keeping things in memory unnecessarily.
*/
@SuppressLint("DumpableNotRegistered") // this will be dumped by container classes
class NotifCollectionCache<V>(
@@ -151,7 +154,7 @@
* purge((c)); // deletes a from the cache and marks b for deletion, etc.
* ```
*/
- fun purge(wantedKeys: List<String>) {
+ fun purge(wantedKeys: Collection<String>) {
for ((key, entry) in cache) {
if (key in wantedKeys) {
entry.resetLives()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 9b075a6..f75163d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -27,6 +27,7 @@
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -49,13 +50,18 @@
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -104,6 +110,8 @@
/** How long we can delay a group while waiting for all children to inflate */
private final long mMaxGroupInflationDelay;
private final BindEventManagerImpl mBindEventManager;
+ private final AppIconProvider mAppIconProvider;
+ private final NotificationIconStyleProvider mNotificationIconStyleProvider;
@Inject
public PreparationCoordinator(
@@ -113,7 +121,9 @@
NotifViewBarn viewBarn,
NotifUiAdjustmentProvider adjustmentProvider,
IStatusBarService service,
- BindEventManagerImpl bindEventManager) {
+ BindEventManagerImpl bindEventManager,
+ AppIconProvider appIconProvider,
+ NotificationIconStyleProvider notificationIconStyleProvider) {
this(
logger,
notifInflater,
@@ -122,6 +132,8 @@
adjustmentProvider,
service,
bindEventManager,
+ appIconProvider,
+ notificationIconStyleProvider,
CHILD_BIND_CUTOFF,
MAX_GROUP_INFLATION_DELAY);
}
@@ -135,6 +147,8 @@
NotifUiAdjustmentProvider adjustmentProvider,
IStatusBarService service,
BindEventManagerImpl bindEventManager,
+ AppIconProvider appIconProvider,
+ NotificationIconStyleProvider notificationIconStyleProvider,
int childBindCutoff,
long maxGroupInflationDelay) {
mLogger = logger;
@@ -146,6 +160,8 @@
mChildBindCutoff = childBindCutoff;
mMaxGroupInflationDelay = maxGroupInflationDelay;
mBindEventManager = bindEventManager;
+ mAppIconProvider = appIconProvider;
+ mNotificationIconStyleProvider = notificationIconStyleProvider;
}
@Override
@@ -155,6 +171,9 @@
() -> mNotifInflatingFilter.invalidateList("adjustmentProviderChanged"));
pipeline.addCollectionListener(mNotifCollectionListener);
+ if (android.app.Flags.notificationsRedesignAppIcons()) {
+ pipeline.addOnBeforeTransformGroupsListener(this::purgeCaches);
+ }
// Inflate after grouping/sorting since that affects what views to inflate.
pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews);
pipeline.addFinalizeFilter(mNotifInflationErrorFilter);
@@ -260,6 +279,29 @@
}
};
+ private void purgeCaches(Collection<ListEntry> entries) {
+ Set<String> wantedPackages = getPackages(entries);
+ mAppIconProvider.purgeCache(wantedPackages);
+ mNotificationIconStyleProvider.purgeCache(wantedPackages);
+ }
+
+ /**
+ * Get all app packages present in {@param entries}.
+ */
+ private static @NonNull Set<String> getPackages(Collection<ListEntry> entries) {
+ Set<String> packages = new HashSet<>();
+ for (ListEntry entry : entries) {
+ NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+ if (notificationEntry == null) {
+ Log.wtf(TAG, "notification entry " + entry.getKey()
+ + " has no representative entry");
+ continue;
+ }
+ packages.add(notificationEntry.getSbn().getPackageName());
+ }
+ return packages;
+ }
+
private void inflateAllRequiredViews(List<ListEntry> entries) {
for (int i = 0, size = entries.size(); i < size; i++) {
ListEntry entry = entries.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 6edffc0..8660cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,9 +29,11 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -44,7 +46,6 @@
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.BooleanFlowOperators;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -77,7 +78,6 @@
private final CommunalSceneInteractor mCommunalSceneInteractor;
private final ShadeInteractor mShadeInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
- private final KeyguardStateController mKeyguardStateController;
private final VisualStabilityCoordinatorLogger mLogger;
private boolean mSleepy = true;
@@ -120,7 +120,6 @@
CommunalSceneInteractor communalSceneInteractor,
ShadeInteractor shadeInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- KeyguardStateController keyguardStateController,
VisualStabilityCoordinatorLogger logger) {
mHeadsUpManager = headsUpManager;
mShadeAnimationInteractor = shadeAnimationInteractor;
@@ -134,7 +133,6 @@
mCommunalSceneInteractor = communalSceneInteractor;
mShadeInteractor = shadeInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
- mKeyguardStateController = keyguardStateController;
mLogger = logger;
dumpManager.registerDumpable(this);
@@ -164,23 +162,17 @@
KeyguardState.LOCKSCREEN),
this::onLockscreenKeyguardStateTransitionValueChanged);
}
-
if (Flags.checkLockscreenGoneTransition()) {
- mKeyguardStateController.addCallback(mKeyguardFadeAwayAnimationCallback);
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+ Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+ this::onLockscreenInGoneTransitionChanged);
}
+
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
- final KeyguardStateController.Callback mKeyguardFadeAwayAnimationCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- onLockscreenInGoneTransitionChanged(
- mKeyguardStateController.isKeyguardFadingAway());
- }
- };
-
// TODO(b/203826051): Ensure stability manager can allow reordering off-screen
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 24b5cf1a..0ddf9f72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.icon
+import android.annotation.WorkerThread
import android.app.ActivityManager
import android.app.Flags
import android.content.Context
@@ -27,20 +28,45 @@
import android.util.Log
import com.android.internal.R
import com.android.launcher3.icons.BaseIconFactory
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
import dagger.Module
import dagger.Provides
+import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
/** A provider used to cache and fetch app icons used by notifications. */
interface AppIconProvider {
+ /**
+ * Loads the icon corresponding to [packageName] into cache, or fetches it from there if already
+ * present. This should only be called from the background.
+ */
@Throws(NameNotFoundException::class)
+ @WorkerThread
fun getOrFetchAppIcon(packageName: String, context: Context): Drawable
+
+ /**
+ * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
+ * still not needed on the next call of this method (made after a timeout of 1s, in case they
+ * happen more frequently than that), they will be purged. This can be done from any thread.
+ */
+ fun purgeCache(wantedPackages: Collection<String>)
}
@SysUISingleton
-class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider {
+class AppIconProviderImpl
+@Inject
+constructor(private val sysuiContext: Context, dumpManager: DumpManager) :
+ AppIconProvider, Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(TAG, this)
+ }
+
private val iconFactory: BaseIconFactory
get() {
val isLowRam = ActivityManager.isLowRamDeviceStatic()
@@ -53,13 +79,42 @@
return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize)
}
+ private val cache = NotifCollectionCache<Drawable>()
+
override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
+ return cache.getOrFetch(packageName) { fetchAppIcon(packageName, context) }
+ }
+
+ @WorkerThread
+ private fun fetchAppIcon(packageName: String, context: Context): BitmapDrawable {
val icon = context.packageManager.getApplicationIcon(packageName)
return BitmapDrawable(
context.resources,
iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE),
)
}
+
+ override fun purgeCache(wantedPackages: Collection<String>) {
+ cache.purge(wantedPackages)
+ }
+
+ override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
+ val pw = pwOrig.asIndenting()
+
+ pw.println("cache information:")
+ pw.withIncreasedIndent { cache.dump(pw, args) }
+
+ val iconFactory = iconFactory
+ pw.println("icon factory information:")
+ pw.withIncreasedIndent {
+ pw.println("fullResIconDpi = ${iconFactory.fullResIconDpi}")
+ pw.println("iconSize = ${iconFactory.iconBitmapSize}")
+ }
+ }
+
+ companion object {
+ const val TAG = "AppIconProviderImpl"
+ }
}
class NoOpIconProvider : AppIconProvider {
@@ -71,6 +126,10 @@
Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
return ColorDrawable(Color.WHITE)
}
+
+ override fun purgeCache(wantedPackages: Collection<String>) {
+ Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 165c1a7..35e38c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -22,9 +22,15 @@
import android.content.pm.ApplicationInfo
import android.service.notification.StatusBarNotification
import android.util.Log
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
import dagger.Module
import dagger.Provides
+import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -33,15 +39,35 @@
* notifications.
*/
interface NotificationIconStyleProvider {
+ /**
+ * Determines whether the [notification] should display the app icon instead of the small icon.
+ * This can result in a binder call, and therefore should only be called from the background.
+ */
@WorkerThread
fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean
+
+ /**
+ * Mark all the entries in the cache that are NOT in [wantedPackages] to be cleared. If they're
+ * still not needed on the next call of this method (made after a timeout of 1s, in case they
+ * happen more frequently than that), they will be purged. This can be done from any thread.
+ */
+ fun purgeCache(wantedPackages: Collection<String>)
}
@SysUISingleton
-class NotificationIconStyleProviderImpl @Inject constructor() : NotificationIconStyleProvider {
+class NotificationIconStyleProviderImpl @Inject constructor(dumpManager: DumpManager) :
+ NotificationIconStyleProvider, Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(TAG, this)
+ }
+
+ private val cache = NotifCollectionCache<Boolean>()
+
override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean {
val packageContext = notification.getPackageContext(context)
- return !belongsToHeadlessSystemApp(packageContext)
+ return cache.getOrFetch(notification.packageName) {
+ !belongsToHeadlessSystemApp(packageContext)
+ }
}
@WorkerThread
@@ -62,6 +88,20 @@
return false
}
}
+
+ override fun purgeCache(wantedPackages: Collection<String>) {
+ cache.purge(wantedPackages)
+ }
+
+ override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
+ val pw = pwOrig.asIndenting()
+ pw.println("cache information:")
+ pw.withIncreasedIndent { cache.dump(pw, args) }
+ }
+
+ companion object {
+ const val TAG = "NotificationIconStyleProviderImpl"
+ }
}
class NoOpIconStyleProvider : NotificationIconStyleProvider {
@@ -73,6 +113,10 @@
Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
return true
}
+
+ override fun purgeCache(wantedPackages: Collection<String>) {
+ Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.")
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index a8c823c..858cac1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -29,9 +29,8 @@
class ConfigurationControllerImpl
@AssistedInject
-constructor(
- @Assisted private val context: Context,
-) : ConfigurationController, StatusBarConfigurationController {
+constructor(@Assisted private val context: Context) :
+ ConfigurationController, StatusBarConfigurationController {
private val listeners: MutableList<ConfigurationListener> = ArrayList()
private val lastConfig = Configuration()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
new file mode 100644
index 0000000..3fd46fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.statusbar.phone
+
+import android.content.res.Configuration
+
+/**
+ * Used to forward a configuration change to other components.
+ *
+ * This is commonly used to propagate configs to [ConfigurationController]. Note that there could be
+ * different configuration forwarder, for example each display, window or group of classes (e.g.
+ * shade window classes).
+ */
+interface ConfigurationForwarder {
+ /** Should be called when a new configuration is received. */
+ fun onConfigurationChanged(newConfiguration: Configuration)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 74c6e72..f7fea7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1535,6 +1535,7 @@
}
public boolean interceptMediaKey(KeyEvent event) {
+ ComposeBouncerFlags.assertInLegacyMode();
return mPrimaryBouncerView.getDelegate() != null
&& mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 93db2db..af98311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,7 +20,6 @@
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
-import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -231,8 +230,7 @@
* @param entry notification that bubble icon was clicked
*/
@Override
- public void onNotificationBubbleIconClicked(NotificationEntry entry) {
- expectNotNull(TAG, "entry", entry);
+ public void onNotificationBubbleIconClicked(@NonNull NotificationEntry entry) {
Runnable action = () -> {
mBubblesManagerOptional.ifPresent(bubblesManager ->
bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -258,9 +256,8 @@
* @param row row for that notification
*/
@Override
- public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
- expectNotNull(TAG, "entry", entry);
- expectNotNull(TAG, "row", row);
+ public void onNotificationClicked(@NonNull NotificationEntry entry,
+ @NonNull ExpandableNotificationRow row) {
mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
mKeyguardStateController.isVisible(),
mNotificationShadeWindowController.getPanelExpanded());
@@ -442,8 +439,7 @@
* @param entry notification entry that is dropped.
*/
@Override
- public void onDragSuccess(NotificationEntry entry) {
- expectNotNull(TAG, "entry", entry);
+ public void onDragSuccess(@NonNull NotificationEntry entry) {
// this method is not responsible for intent sending.
// will focus follow operation only after drag-and-drop that notification.
final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -534,10 +530,8 @@
}
@Override
- public void startNotificationGutsIntent(final Intent intent, final int appUid,
- ExpandableNotificationRow row) {
- expectNotNull(TAG, "intent", intent);
- expectNotNull(TAG, "row", row);
+ public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
+ @NonNull ExpandableNotificationRow row) {
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 09e191d..92d0ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.core.StatusBarOrchestrator
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -45,7 +46,7 @@
import kotlinx.coroutines.CoroutineScope
/** Similar in purpose to [StatusBarModule], but scoped only to phones */
-@Module
+@Module(includes = [PrivacyDotViewControllerModule::class])
interface StatusBarPhoneModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index cec77c1..1bb4e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -16,16 +16,15 @@
import android.content.res.Configuration;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
/**
* Common listener for configuration or subsets of configuration changes (like density or
* font scaling), providing easy static dependence on these events.
*/
-public interface ConfigurationController extends CallbackController<ConfigurationListener> {
-
- /** Alert controller of a change in the configuration. */
- void onConfigurationChanged(Configuration newConfiguration);
+public interface ConfigurationController extends CallbackController<ConfigurationListener>,
+ ConfigurationForwarder {
/** Alert controller of a change in between light and dark themes. */
void notifyThemeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index c256e64..00116aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -71,6 +72,7 @@
new UpdateMonitorCallback();
private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
private final KeyguardUpdateMonitorLogger mLogger;
+ private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
private boolean mCanDismissLockScreen;
private boolean mShowing;
@@ -123,6 +125,7 @@
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
KeyguardUpdateMonitorLogger logger,
DumpManager dumpManager,
+ Lazy<KeyguardInteractor> keyguardInteractor,
FeatureFlags featureFlags,
SelectedUserInteractor userInteractor) {
mContext = context;
@@ -133,6 +136,7 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
mFeatureFlags = featureFlags;
+ mKeyguardInteractorLazy = keyguardInteractor;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -354,6 +358,7 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
keyguardGoingAway ? 1 : 0);
mKeyguardGoingAway = keyguardGoingAway;
+ mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway);
invokeForEachCallback(Callback::onKeyguardGoingAwayChanged);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b81af86..c7bd5a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.policy.BatteryControllerLogger;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
@@ -186,6 +187,13 @@
DevicePostureControllerImpl devicePostureControllerImpl);
/** */
+ @Binds
+ @SysUISingleton
+ @GlobalConfig
+ ConfigurationForwarder provideGlobalConfigurationForwarder(
+ @GlobalConfig ConfigurationController configurationController);
+
+ /** */
@Provides
@SysUISingleton
@GlobalConfig
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index e89a31f..618722a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -27,7 +27,10 @@
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@Composable
fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -48,7 +51,17 @@
),
)
val recognizer = rememberBackGestureRecognizer(LocalContext.current.resources)
- GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+ val gestureUiState: Flow<GestureUiState> =
+ remember(recognizer) {
+ GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+ it.toGestureUiState(
+ progressStartMark = "",
+ progressEndMark = "",
+ successAnimation = R.raw.trackpad_back_success,
+ )
+ }
+ }
+ GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 7899f5b..11e1ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -17,6 +17,7 @@
package com.android.systemui.touchpad.tutorial.ui.composable
import androidx.activity.compose.BackHandler
+import androidx.annotation.RawRes
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
@@ -31,23 +32,49 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
+import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
+import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
+import kotlinx.coroutines.flow.Flow
-fun GestureState.toTutorialActionState(): TutorialActionState {
+sealed interface GestureUiState {
+ data object NotStarted : GestureUiState
+
+ data class Finished(@RawRes val successAnimation: Int) : GestureUiState
+
+ data class InProgress(
+ val progress: Float = 0f,
+ val progressStartMark: String = "",
+ val progressEndMark: String = "",
+ ) : GestureUiState
+}
+
+fun GestureState.toGestureUiState(
+ progressStartMark: String,
+ progressEndMark: String,
+ successAnimation: Int,
+): GestureUiState {
+ return when (this) {
+ GestureState.NotStarted -> NotStarted
+ is GestureState.InProgress ->
+ GestureUiState.InProgress(this.progress, progressStartMark, progressEndMark)
+ is GestureState.Finished -> GestureUiState.Finished(successAnimation)
+ }
+}
+
+fun GestureUiState.toTutorialActionState(): TutorialActionState {
return when (this) {
NotStarted -> TutorialActionState.NotStarted
// progress is disabled for now as views are not ready to handle varying progress
- is InProgress -> TutorialActionState.InProgress(0f)
- Finished -> TutorialActionState.Finished
+ is GestureUiState.InProgress -> TutorialActionState.InProgress(progress = 0f)
+ is Finished -> TutorialActionState.Finished
}
}
@@ -55,15 +82,13 @@
fun GestureTutorialScreen(
screenConfig: TutorialScreenConfig,
gestureRecognizer: GestureRecognizer,
+ gestureUiStateFlow: Flow<GestureUiState>,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
BackHandler(onBack = onBack)
- var gestureState: GestureState by remember { mutableStateOf(NotStarted) }
var easterEggTriggered by remember { mutableStateOf(false) }
- LaunchedEffect(gestureRecognizer) {
- gestureRecognizer.addGestureStateCallback { gestureState = it }
- }
+ val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted)
val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered = true }
val gestureHandler =
remember(gestureRecognizer) { TouchpadGestureHandler(gestureRecognizer, easterEggMonitor) }
@@ -84,7 +109,7 @@
@Composable
private fun TouchpadGesturesHandlingBox(
gestureHandler: TouchpadGestureHandler,
- gestureState: GestureState,
+ gestureState: GestureUiState,
easterEggTriggered: Boolean,
resetEasterEggFlag: () -> Unit,
modifier: Modifier = Modifier,
@@ -110,7 +135,7 @@
.pointerInteropFilter(
onTouchEvent = { event ->
// FINISHED is the final state so we don't need to process touches anymore
- if (gestureState == Finished) {
+ if (gestureState is Finished) {
false
} else {
gestureHandler.onMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 3ddf760..05871ce 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -25,8 +25,11 @@
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@Composable
fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@
),
)
val recognizer = rememberHomeGestureRecognizer(LocalContext.current.resources)
- GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+ val gestureUiState: Flow<GestureUiState> =
+ remember(recognizer) {
+ GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+ it.toGestureUiState(
+ progressStartMark = "",
+ progressEndMark = "",
+ successAnimation = R.raw.trackpad_home_success,
+ )
+ }
+ }
+ GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 30a21bf..4fd1644 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -25,8 +25,11 @@
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@Composable
fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@
),
)
val recognizer = rememberRecentAppsGestureRecognizer(LocalContext.current.resources)
- GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
+ val gestureUiState: Flow<GestureUiState> =
+ remember(recognizer) {
+ GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
+ it.toGestureUiState(
+ progressStartMark = "",
+ progressEndMark = "",
+ successAnimation = R.raw.trackpad_recent_apps_success,
+ )
+ }
+ }
+ GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
index 80f8003..024048c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
@@ -33,6 +33,10 @@
gestureStateChangedCallback = callback
}
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt
new file mode 100644
index 0000000..23e31b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+class GestureFlowAdapter(gestureRecognizer: GestureRecognizer) {
+
+ val gestureStateAsFlow: Flow<GestureState> = conflatedCallbackFlow {
+ val callback: (GestureState) -> Unit = { trySend(it) }
+ gestureRecognizer.addGestureStateCallback(callback)
+ awaitClose { gestureRecognizer.clearGestureStateCallback() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
index d146268..68a2ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt
@@ -22,6 +22,8 @@
/** Based on passed [MotionEvent]s recognizes different states of gesture and notifies callback. */
interface GestureRecognizer : Consumer<MotionEvent> {
fun addGestureStateCallback(callback: (GestureState) -> Unit)
+
+ fun clearGestureStateCallback()
}
fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index 2b84a4c..b804b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -29,6 +29,10 @@
gestureStateChangedCallback = callback
}
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index 69b7c5e..7d484ee 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -38,6 +38,10 @@
gestureStateChangedCallback = callback
}
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
val gestureState = distanceTracker.processEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7c5116d..07509e6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -64,6 +64,8 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RotateDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.Debug;
@@ -115,6 +117,7 @@
import com.android.internal.view.RotationPolicy;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
@@ -140,6 +143,7 @@
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
+import com.google.android.msdl.domain.MSDLPlayer;
import com.google.common.collect.ImmutableList;
import dagger.Lazy;
@@ -315,6 +319,7 @@
private final Lazy<SecureSettings> mSecureSettings;
private int mDialogTimeoutMillis;
private final VibratorHelper mVibratorHelper;
+ private final MSDLPlayer mMSDLPlayer;
private final com.android.systemui.util.time.SystemClock mSystemClock;
private final VolumePanelFlag mVolumePanelFlag;
private final VolumeDialogInteractor mInteractor;
@@ -340,12 +345,14 @@
DumpManager dumpManager,
Lazy<SecureSettings> secureSettings,
VibratorHelper vibratorHelper,
+ MSDLPlayer msdlPlayer,
com.android.systemui.util.time.SystemClock systemClock,
VolumeDialogInteractor interactor) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
mVibratorHelper = vibratorHelper;
+ mMSDLPlayer = msdlPlayer;
mSystemClock = systemClock;
mShouldListenForJank = shouldListenForJank;
mController = volumeDialogController;
@@ -652,6 +659,11 @@
mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
}
+ if (Flags.hideRingerButtonInSingleVolumeMode() && AudioSystem.isSingleVolume(mContext)) {
+ mRingerAndDrawerContainer.setVisibility(INVISIBLE);
+ mRinger.setVisibility(INVISIBLE);
+ }
+
mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
mSelectedRingerContainer = mDialog.findViewById(
R.id.volume_new_ringer_active_icon_container);
@@ -927,7 +939,7 @@
}
private void addSliderHapticsToRow(VolumeRow row) {
- row.createPlugin(mVibratorHelper, mSystemClock);
+ row.createPlugin(mVibratorHelper, mMSDLPlayer, mSystemClock);
HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
}
@@ -2337,10 +2349,31 @@
return;
}
- final ColorDrawable solidDrawable = new ColorDrawable(
+ LayerDrawable background;
+ // mRingerAndDrawerContainer has rounded corner.
+ // But when it's not visible, mTopContainer needs to have rounded corner.
+ if (Flags.hideRingerButtonInSingleVolumeMode()
+ && mRingerAndDrawerContainer.getVisibility() != VISIBLE
+ ) {
+ float[] radius = new float[] {
+ mDialogCornerRadius, mDialogCornerRadius, // Top-left corner
+ mDialogCornerRadius, mDialogCornerRadius, // Top-right corner
+ 0, 0, // Bottom-right corner
+ 0, 0 // Bottom-left corner
+ };
+
+ ShapeDrawable roundedDrawable = new ShapeDrawable(
+ new RoundRectShape(radius, null, null));
+ roundedDrawable.getPaint().setColor(Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.colorSurface));
+
+ background = new LayerDrawable(new Drawable[] { roundedDrawable });
+ } else {
+ final ColorDrawable solidDrawable = new ColorDrawable(
Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface));
- final LayerDrawable background = new LayerDrawable(new Drawable[] { solidDrawable });
+ background = new LayerDrawable(new Drawable[] { solidDrawable });
+ }
// Size the solid color to match the primary volume row. In landscape, extend it upwards
// slightly so that it fills in the bottom corners of the ringer icon, whose background is
@@ -2707,11 +2740,13 @@
void createPlugin(
VibratorHelper vibratorHelper,
+ MSDLPlayer msdlPlayer,
com.android.systemui.util.time.SystemClock systemClock) {
if (mHapticPlugin != null) return;
mHapticPlugin = new SeekbarHapticPlugin(
vibratorHelper,
+ msdlPlayer,
systemClock,
sSliderHapticFeedbackConfig,
sSliderTrackerConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index ed8de69..2009143 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -50,6 +50,8 @@
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -121,6 +123,7 @@
DumpManager dumpManager,
Lazy<SecureSettings> secureSettings,
VibratorHelper vibratorHelper,
+ MSDLPlayer msdlPlayer,
SystemClock systemClock,
VolumeDialogInteractor interactor) {
if (Flags.volumeRedesign()) {
@@ -144,6 +147,7 @@
dumpManager,
secureSettings,
vibratorHelper,
+ msdlPlayer,
systemClock,
interactor);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
new file mode 100644
index 0000000..7265b821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.volume.dialog.ringer.domain
+
+import android.media.AudioManager
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.provider.Settings
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Exposes [VolumeDialogRingerModel]. */
+@VolumeDialog
+class VolumeDialogRingerInteractor
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ private val controller: VolumeDialogController,
+) {
+
+ val ringerModel: Flow<VolumeDialogRingerModel> =
+ volumeDialogStateInteractor.volumeDialogState
+ .mapNotNull { toRingerModel(it) }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+
+ private fun toRingerModel(state: VolumeDialogStateModel): VolumeDialogRingerModel? {
+ return state.streamModels[AudioManager.STREAM_RING]?.let {
+ VolumeDialogRingerModel(
+ availableModes =
+ mutableListOf(RingerMode(RINGER_MODE_NORMAL), RingerMode(RINGER_MODE_SILENT))
+ .also { list ->
+ if (controller.hasVibrator()) {
+ list.add(RingerMode(RINGER_MODE_VIBRATE))
+ }
+ },
+ currentRingerMode = RingerMode(state.ringerModeInternal),
+ isEnabled =
+ !(state.zenMode == Settings.Global.ZEN_MODE_ALARMS ||
+ state.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS ||
+ (state.zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS &&
+ state.disallowRinger)),
+ isMuted = it.level == 0 || it.muted,
+ level = it.level,
+ levelMax = it.levelMax,
+ )
+ }
+ }
+
+ fun setRingerMode(ringerMode: RingerMode) {
+ controller.setRingerMode(ringerMode.value, false)
+ }
+
+ fun scheduleTouchFeedback() {
+ controller.scheduleTouchFeedback()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
new file mode 100644
index 0000000..cf23f1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.volume.dialog.ringer.shared.model
+
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models the state of the volume dialog ringer. */
+data class VolumeDialogRingerModel(
+ val availableModes: List<RingerMode>,
+ /** Current ringer mode internal */
+ val currentRingerMode: RingerMode,
+ /** whether the ringer is allowed given the current ZenMode */
+ val isEnabled: Boolean,
+ /** Whether the current ring stream level is zero or the controller state is muted */
+ val isMuted: Boolean,
+ /** Ring stream level */
+ val level: Int,
+ /** Ring stream maximum level */
+ val levelMax: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02d0b57..8039e00 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -281,13 +281,13 @@
void initSplitScreen(SplitScreen splitScreen) {
mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
@Override
- public void onFinishedWakingUp() {
- splitScreen.onFinishedWakingUp();
+ public void onStartedGoingToSleep() {
+ splitScreen.onStartedGoingToSleep();
}
@Override
- public void onStartedGoingToSleep() {
- splitScreen.onStartedGoingToSleep();
+ public void onStartedWakingUp() {
+ splitScreen.onStartedWakingUp();
}
});
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 3d1a0d0..96f4a60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -481,6 +481,25 @@
verify(mAuthController).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
}
+ @Test
+ @EnableFlags(android.hardware.biometrics.Flags.FLAG_SCREEN_OFF_UNLOCK_UDFPS)
+ public void udfpsLongPress_triggeredWhenDoze() {
+ // GIVEN device is DOZE
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN udfps long-press is triggered
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, 100, 100,
+ new float[]{0, 1, 2, 3, 4});
+
+ // THEN the pulse is NOT dropped
+ verify(mDozeLog, never()).tracePulseDropped(anyString(), any());
+
+ // WHEN the screen state is OFF
+ mTriggers.onScreenState(Display.STATE_OFF);
+
+ // THEN aod interrupt never be sent
+ verify(mAuthController, never()).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
+ }
@Test
public void udfpsLongPress_dozeState_notRegistered() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 6b60740..7383faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -21,6 +21,9 @@
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -41,13 +44,17 @@
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -76,6 +83,7 @@
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { context })
}
private val testScope = kosmos.testScope
@@ -253,7 +261,7 @@
whenever(
mockRoleManager.getRoleHoldersAsUser(
RoleManager.ROLE_HOME,
- fakeUserTracker.userHandle
+ fakeUserTracker.userHandle,
)
)
.thenReturn(listOf(TestShortcuts.currentAppPackageName))
@@ -283,15 +291,25 @@
val activeUiState = uiState as ShortcutsUiState.Active
assertThat(activeUiState.shortcutCategories)
.containsExactly(
- ShortcutCategory(
- System,
- subCategoryWithShortcutLabels("first Foo shortcut1"),
- subCategoryWithShortcutLabels("second foO shortcut2")
+ ShortcutCategoryUi(
+ label = "System",
+ iconSource = IconSource(imageVector = Icons.Default.Tv),
+ shortcutCategory =
+ ShortcutCategory(
+ System,
+ subCategoryWithShortcutLabels("first Foo shortcut1"),
+ subCategoryWithShortcutLabels("second foO shortcut2"),
+ ),
),
- ShortcutCategory(
- MultiTasking,
- subCategoryWithShortcutLabels("third FoO shortcut1")
- )
+ ShortcutCategoryUi(
+ label = "Multitasking",
+ iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
+ shortcutCategory =
+ ShortcutCategory(
+ MultiTasking,
+ subCategoryWithShortcutLabels("third FoO shortcut1"),
+ ),
+ ),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index e981d62..cad22d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -16,11 +16,11 @@
package com.android.systemui.keyguard;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index dcf32a5..51c8525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -38,11 +38,11 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -61,7 +61,7 @@
private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
+ return ArgumentMatchers.any<T>()
}
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index bdd8dc8..2aa300d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -21,7 +21,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2f41ac17..338ed75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,9 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -194,7 +193,7 @@
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
}
@Test
@@ -207,7 +206,7 @@
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
}
@Test
@@ -220,7 +219,7 @@
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(0))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
}
@Test
@@ -233,7 +232,7 @@
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(0))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
}
@Test
@@ -243,14 +242,14 @@
// success registering skin thermal event listener
when(mThermalServiceMock.registerThermalEventListenerWithType(
- anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(true);
+ any(), eq(Temperature.TYPE_SKIN))).thenReturn(true);
mPowerUI.doSkinThermalEventListenerRegistration();
// verify registering skin thermal event listener, return true (success)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
// Settings SHOW_TEMPERATURE_WARNING is set to 0
Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
@@ -259,7 +258,7 @@
// verify unregistering skin thermal event listener
TestableLooper.get(this).processAllMessages();
- verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
+ verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(any());
}
@Test
@@ -269,14 +268,14 @@
// fail registering skin thermal event listener
when(mThermalServiceMock.registerThermalEventListenerWithType(
- anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(false);
+ any(), eq(Temperature.TYPE_SKIN))).thenReturn(false);
mPowerUI.doSkinThermalEventListenerRegistration();
// verify registering skin thermal event listener, return false (fail)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
// Settings SHOW_TEMPERATURE_WARNING is set to 0
Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
@@ -285,7 +284,7 @@
// verify that cannot unregister listener (current state is unregistered)
TestableLooper.get(this).processAllMessages();
- verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
+ verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(any());
// Settings SHOW_TEMPERATURE_WARNING is set to 1
Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
@@ -295,7 +294,7 @@
// verify that can register listener (current state is unregistered)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(2))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_SKIN));
}
@Test
@@ -305,14 +304,14 @@
// success registering usb thermal event listener
when(mThermalServiceMock.registerThermalEventListenerWithType(
- anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(true);
+ any(), eq(Temperature.TYPE_USB_PORT))).thenReturn(true);
mPowerUI.doUsbThermalEventListenerRegistration();
// verify registering usb thermal event listener, return true (success)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
// Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
@@ -320,7 +319,7 @@
// verify unregistering usb thermal event listener
mPowerUI.doUsbThermalEventListenerRegistration();
TestableLooper.get(this).processAllMessages();
- verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
+ verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(any());
}
@Test
@@ -330,14 +329,14 @@
// fail registering usb thermal event listener
when(mThermalServiceMock.registerThermalEventListenerWithType(
- anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(false);
+ any(), eq(Temperature.TYPE_USB_PORT))).thenReturn(false);
mPowerUI.doUsbThermalEventListenerRegistration();
// verify registering usb thermal event listener, return false (fail)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(1))
- .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
+ .registerThermalEventListenerWithType(any(), eq(Temperature.TYPE_USB_PORT));
// Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
@@ -346,7 +345,7 @@
// verify that cannot unregister listener (current state is unregistered)
TestableLooper.get(this).processAllMessages();
- verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
+ verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(any());
// Settings SHOW_USB_TEMPERATURE_ALARM is set to 1
Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
@@ -356,7 +355,7 @@
// verify that can register listener (current state is unregistered)
TestableLooper.get(this).processAllMessages();
verify(mThermalServiceMock, times(2)).registerThermalEventListenerWithType(
- anyObject(), eq(Temperature.TYPE_USB_PORT));
+ any(), eq(Temperature.TYPE_USB_PORT));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index dad65f5..f6d5732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -25,7 +25,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 748c7d9..296478b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -36,7 +36,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
deleted file mode 100644
index 3756ec1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.app.ActivityTaskManager.RootTaskInfo
-import android.app.IActivityTaskManager
-import android.content.ComponentName
-import android.content.Context
-import android.graphics.Rect
-import android.os.UserHandle
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import com.android.systemui.screenshot.policy.ActivityType.Home
-import com.android.systemui.screenshot.policy.ActivityType.Undefined
-import com.android.systemui.screenshot.policy.WindowingMode.FullScreen
-import com.android.systemui.screenshot.policy.WindowingMode.PictureInPicture
-import com.android.systemui.screenshot.policy.newChildTask
-import com.android.systemui.screenshot.policy.newRootTaskInfo
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Test
-import org.junit.runner.RunWith
-
-// The following values are chosen to be distinct from commonly seen real values
-private const val DISPLAY_ID = 100
-private const val PRIMARY_USER = 2000
-private const val MANAGED_PROFILE_USER = 3000
-
-@RunWith(AndroidTestingRunner::class)
-class ScreenshotPolicyImplTest : SysuiTestCase() {
-
- @Test
- fun testToDisplayContentInfo() {
- assertThat(fullScreenWorkProfileTask.toDisplayContentInfo())
- .isEqualTo(
- DisplayContentInfo(
- ComponentName(
- "com.google.android.apps.nbu.files",
- "com.google.android.apps.nbu.files.home.HomeActivity"
- ),
- Rect(0, 0, 1080, 2400),
- UserHandle.of(MANAGED_PROFILE_USER),
- 65
- )
- )
- }
-
- @Test
- fun findPrimaryContent_ignoresPipTask() = runBlocking {
- val policy =
- fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = false,
- tasks = listOf(pipTask, fullScreenWorkProfileTask, launcherTask, emptyTask)
- )
-
- val info = policy.findPrimaryContent(DISPLAY_ID)
- assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
- }
-
- @Test
- fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
- val policy =
- fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = true,
- tasks = listOf(fullScreenWorkProfileTask, launcherTask, emptyTask)
- )
-
- val info = policy.findPrimaryContent(DISPLAY_ID)
- assertThat(info).isEqualTo(policy.systemUiContent)
- }
-
- @Test
- fun findPrimaryContent_emptyTaskList() = runBlocking {
- val policy = fakeTasksPolicyImpl(mContext, shadeExpanded = false, tasks = listOf())
-
- val info = policy.findPrimaryContent(DISPLAY_ID)
- assertThat(info).isEqualTo(policy.systemUiContent)
- }
-
- @Test
- fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
- val policy =
- fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = false,
- tasks = listOf(launcherTask, fullScreenWorkProfileTask, emptyTask)
- )
-
- val info = policy.findPrimaryContent(DISPLAY_ID)
- assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
- }
-
- private fun fakeTasksPolicyImpl(
- context: Context,
- shadeExpanded: Boolean,
- tasks: List<RootTaskInfo>
- ): ScreenshotPolicyImpl {
- val userManager = mock<UserManager>()
- val atmService = mock<IActivityTaskManager>()
- val dispatcher = Dispatchers.Unconfined
- val displayTracker = FakeDisplayTracker(context)
-
- return object :
- ScreenshotPolicyImpl(context, userManager, atmService, dispatcher, displayTracker) {
- override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
- override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
- override suspend fun isNotificationShadeExpanded() = shadeExpanded
- }
- }
-
- private val pipTask =
- newRootTaskInfo(
- taskId = 66,
- userId = PRIMARY_USER,
- displayId = DISPLAY_ID,
- bounds = Rect(628, 1885, 1038, 2295),
- windowingMode = PictureInPicture,
- topActivity = ComponentName.unflattenFromString(YOUTUBE_PIP_ACTIVITY),
- ) {
- listOf(newChildTask(taskId = 66, userId = 0, name = YOUTUBE_HOME_ACTIVITY))
- }
-
- private val fullScreenWorkProfileTask =
- newRootTaskInfo(
- taskId = 65,
- userId = MANAGED_PROFILE_USER,
- displayId = DISPLAY_ID,
- bounds = Rect(0, 0, 1080, 2400),
- windowingMode = FullScreen,
- topActivity = ComponentName.unflattenFromString(FILES_HOME_ACTIVITY),
- ) {
- listOf(
- newChildTask(taskId = 65, userId = MANAGED_PROFILE_USER, name = FILES_HOME_ACTIVITY)
- )
- }
- private val launcherTask =
- newRootTaskInfo(
- taskId = 1,
- userId = PRIMARY_USER,
- displayId = DISPLAY_ID,
- activityType = Home,
- windowingMode = FullScreen,
- bounds = Rect(0, 0, 1080, 2400),
- topActivity = ComponentName.unflattenFromString(LAUNCHER_ACTIVITY),
- ) {
- listOf(newChildTask(taskId = 1, userId = 0, name = LAUNCHER_ACTIVITY))
- }
-
- private val emptyTask =
- newRootTaskInfo(
- taskId = 2,
- userId = PRIMARY_USER,
- displayId = DISPLAY_ID,
- visible = false,
- running = false,
- numActivities = 0,
- activityType = Undefined,
- bounds = Rect(0, 0, 1080, 2400),
- ) {
- listOf(
- newChildTask(taskId = 3, name = ""),
- newChildTask(taskId = 4, name = ""),
- )
- }
-}
-
-private const val YOUTUBE_HOME_ACTIVITY =
- "com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"
-
-private const val FILES_HOME_ACTIVITY =
- "com.google.android.apps.nbu.files/" + "com.google.android.apps.nbu.files.home.HomeActivity"
-
-private const val YOUTUBE_PIP_ACTIVITY =
- "com.google.android.youtube/" +
- "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
-
-private const val LAUNCHER_ACTIVITY =
- "com.google.android.apps.nexuslauncher/" +
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
index 040a9e9..8bd8b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package systemui.shared.clocks.view
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index e9222c3e..3ad0605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -19,8 +19,8 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2b5e014..b730b37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -21,8 +21,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index a75d7b2..da0029f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -21,6 +21,8 @@
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -777,6 +779,24 @@
}
@Test
+ public void indicationAreaHidden_untilBatteryInfoArrives() {
+ createController();
+ // level of -1 indicates missing info
+ BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_UNKNOWN,
+ -1 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
+ 0 /* maxChargingWattage */, true /* present */);
+
+ mController.setVisible(true);
+ mStatusBarStateListener.onDozingChanged(true);
+ reset(mIndicationArea);
+
+ mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+ // VISIBLE is always called first
+ verify(mIndicationArea).setVisibility(VISIBLE);
+ verify(mIndicationArea).setVisibility(GONE);
+ }
+
+ @Test
public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index b18b7f8..72ffa0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,41 +16,39 @@
package com.android.systemui.statusbar.commandline
-import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
+ return any<T>()
}
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@SmallTest
class CommandRegistryTest : SysuiTestCase() {
lateinit var registry: CommandRegistry
- val inLineExecutor = object : Executor {
- override fun execute(command: Runnable) {
- command.run()
+ val inLineExecutor =
+ object : Executor {
+ override fun execute(command: Runnable) {
+ command.run()
+ }
}
- }
val writer: PrintWriter = PrintWriter(StringWriter())
@@ -83,11 +81,9 @@
}
class FakeCommand() : Command {
- override fun execute(pw: PrintWriter, args: List<String>) {
- }
+ override fun execute(pw: PrintWriter, args: List<String>) {}
- override fun help(pw: PrintWriter) {
- }
+ override fun help(pw: PrintWriter) {}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 7bd77a6..5fce08b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -18,7 +18,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import android.os.HandlerThread;
import android.telephony.SubscriptionInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 83dbfa0..b00f9e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -25,10 +25,10 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -297,7 +297,8 @@
assertNotNull(mDefaultCallbackInWifiTracker);
assertNotNull(mDefaultCallbackInNetworkController);
verify(mMockCm, atLeastOnce()).registerNetworkCallback(
- isA(NetworkRequest.class), callbackArg.capture(), isA(Handler.class));
+ isA(NetworkRequest.class), callbackArg.capture(),
+ isA(Handler.class));
mNetworkCallback = callbackArg.getValue();
assertNotNull(mNetworkCallback);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 0b5f8d5..723c0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -74,22 +74,31 @@
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
import java.util.Optional
-import junit.framework.Assert
import kotlin.test.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
+import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.invocation.InvocationOnMock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -99,7 +108,7 @@
NotificationChannel(
TEST_CHANNEL_ID,
TEST_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT
+ NotificationManager.IMPORTANCE_DEFAULT,
)
private val kosmos = testKosmos()
@@ -146,7 +155,7 @@
MockitoAnnotations.initMocks(this)
allowTestableLooperAsMainThread()
helper = NotificationTestHelper(mContext, mDependency)
- Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+ whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
windowRootViewVisibilityInteractor =
WindowRootViewVisibilityInteractor(
testScope.backgroundScope,
@@ -185,12 +194,12 @@
deviceProvisionedController,
metricsLogger,
headsUpManager,
- activityStarter
+ activityStarter,
)
gutsManager.setUpWithPresenter(
presenter,
notificationListContainer,
- onSettingsClickListener
+ onSettingsClickListener,
)
gutsManager.setNotificationActivityStarter(notificationActivityStarter)
gutsManager.start()
@@ -198,49 +207,31 @@
@Test
fun testOpenAndCloseGuts() {
- val guts = Mockito.spy(NotificationGuts(mContext))
- Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
- ->
+ val guts = spy(NotificationGuts(mContext))
+ whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
handler.post((invocation.arguments[0] as Runnable))
null
}
// Test doesn't support animation since the guts view is not attached.
- Mockito.doNothing()
- .`when`(guts)
- .openControls(
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.any(Runnable::class.java)
- )
+ doNothing()
+ .whenever(guts)
+ .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
val realRow = createTestNotificationRow()
val menuItem = createTestMenuItem(realRow)
- val row = Mockito.spy(realRow)
- Mockito.`when`(row!!.windowToken).thenReturn(Binder())
- Mockito.`when`(row.guts).thenReturn(guts)
+ val row = spy(realRow)
+ whenever(row!!.windowToken).thenReturn(Binder())
+ whenever(row.guts).thenReturn(guts)
Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
executor.runAllReady()
- verify(guts)
- .openControls(
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.any(Runnable::class.java)
- )
+ verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
verify(headsUpManager).setGutsShown(realRow!!.entry, true)
assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
verify(guts)
- .closeControls(
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean()
- )
- verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+ .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
+ verify(row, times(1)).setGutsView(any())
executor.runAllReady()
verify(headsUpManager).setGutsShown(realRow.entry, false)
}
@@ -250,7 +241,7 @@
// First, start out lockscreen or shade as not visible
setIsLockscreenOrShadeVisible(false)
testScope.testScheduler.runCurrent()
- val guts = Mockito.mock(NotificationGuts::class.java)
+ val guts = mock<NotificationGuts>()
gutsManager.exposedGuts = guts
// WHEN the lockscreen or shade becomes visible
@@ -258,15 +249,9 @@
testScope.testScheduler.runCurrent()
// THEN the guts are not closed
- verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
- verify(guts, Mockito.never())
- .closeControls(
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean()
- )
+ verify(guts, never()).removeCallbacks(any())
+ verify(guts, never())
+ .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
}
@Test
@@ -274,7 +259,7 @@
// First, start out lockscreen or shade as visible
setIsLockscreenOrShadeVisible(true)
testScope.testScheduler.runCurrent()
- val guts = Mockito.mock(NotificationGuts::class.java)
+ val guts = mock<NotificationGuts>()
gutsManager.exposedGuts = guts
// WHEN the lockscreen or shade is no longer visible
@@ -282,14 +267,14 @@
testScope.testScheduler.runCurrent()
// THEN the guts are closed
- verify(guts).removeCallbacks(ArgumentMatchers.any())
+ verify(guts).removeCallbacks(anyOrNull())
verify(guts)
.closeControls(
- /* leavebehinds= */ ArgumentMatchers.eq(true),
- /* controls= */ ArgumentMatchers.eq(true),
- /* x= */ ArgumentMatchers.anyInt(),
- /* y= */ ArgumentMatchers.anyInt(),
- /* force= */ ArgumentMatchers.eq(true)
+ /* leavebehinds= */ eq(true),
+ /* controls= */ eq(true),
+ /* x= */ any<Int>(),
+ /* y= */ any<Int>(),
+ /* force= */ eq(true),
)
}
@@ -304,95 +289,68 @@
testScope.testScheduler.runCurrent()
// THEN the list container is reset
- verify(notificationListContainer)
- .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+ verify(notificationListContainer).resetExposedMenuView(any<Boolean>(), any<Boolean>())
}
@Test
fun testChangeDensityOrFontScale() {
- val guts = Mockito.spy(NotificationGuts(mContext))
- Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
- ->
+ val guts = spy(NotificationGuts(mContext))
+ whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
handler.post((invocation.arguments[0] as Runnable))
null
}
// Test doesn't support animation since the guts view is not attached.
- Mockito.doNothing()
- .`when`(guts)
- .openControls(
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.any(Runnable::class.java)
- )
+ doNothing()
+ .whenever(guts)
+ .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
val realRow = createTestNotificationRow()
val menuItem = createTestMenuItem(realRow)
- val row = Mockito.spy(realRow)
- Mockito.`when`(row!!.windowToken).thenReturn(Binder())
- Mockito.`when`(row.guts).thenReturn(guts)
- Mockito.doNothing().`when`(row).ensureGutsInflated()
+ val row = spy(realRow)
+ whenever(row!!.windowToken).thenReturn(Binder())
+ whenever(row.guts).thenReturn(guts)
+ doNothing().whenever(row).ensureGutsInflated()
val realEntry = realRow!!.entry
- val entry = Mockito.spy(realEntry)
- Mockito.`when`(entry.row).thenReturn(row)
- Mockito.`when`(entry.getGuts()).thenReturn(guts)
+ val entry = spy(realEntry)
+ whenever(entry.row).thenReturn(row)
+ whenever(entry.getGuts()).thenReturn(guts)
Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
executor.runAllReady()
- verify(guts)
- .openControls(
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.any(Runnable::class.java)
- )
+ verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
// called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
- verify(row).setGutsView(ArgumentMatchers.any())
+ verify(row).setGutsView(any())
row.onDensityOrFontScaleChanged()
gutsManager.onDensityOrFontScaleChanged(entry)
executor.runAllReady()
gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
verify(guts)
- .closeControls(
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyBoolean()
- )
+ .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
// called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
- verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+ verify(row, times(2)).setGutsView(any())
}
@Test
fun testAppOpsSettingsIntent_camera() {
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_CAMERA)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
}
@Test
fun testAppOpsSettingsIntent_mic() {
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_RECORD_AUDIO)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
}
@Test
@@ -400,30 +358,22 @@
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_CAMERA)
ops.add(AppOpsManager.OP_RECORD_AUDIO)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
}
@Test
fun testAppOpsSettingsIntent_overlay() {
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action)
}
@Test
@@ -432,15 +382,11 @@
ops.add(AppOpsManager.OP_CAMERA)
ops.add(AppOpsManager.OP_RECORD_AUDIO)
ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
}
@Test
@@ -448,15 +394,11 @@
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_CAMERA)
ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
}
@Test
@@ -464,112 +406,108 @@
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_RECORD_AUDIO)
ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
- gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(notificationActivityStarter, Mockito.times(1))
- .startNotificationGutsIntent(
- captor.capture(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>())
+ val captor = argumentCaptor<Intent>()
+ verify(notificationActivityStarter, times(1))
+ .startNotificationGutsIntent(captor.capture(), any<Int>(), any())
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
}
@Test
@Throws(Exception::class)
fun testInitializeNotificationInfoView_highPriority() {
- val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
- val row = Mockito.spy(helper.createRow())
+ val notificationInfoView = mock<NotificationInfo>()
+ val row = spy(helper.createRow())
val entry = row.entry
NotificationEntryHelper.modifyRanking(entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
- Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
- Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+ whenever(row.getIsNonblockable()).thenReturn(false)
+ whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
verify(notificationInfoView)
.bindNotification(
- ArgumentMatchers.any(PackageManager::class.java),
- ArgumentMatchers.any(INotificationManager::class.java),
- ArgumentMatchers.eq(onUserInteractionCallback),
- ArgumentMatchers.eq(channelEditorDialogController),
- ArgumentMatchers.eq(statusBarNotification.packageName),
- ArgumentMatchers.any(NotificationChannel::class.java),
- ArgumentMatchers.eq(entry),
- ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
- ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
- ArgumentMatchers.any(UiEventLogger::class.java),
- ArgumentMatchers.eq(true),
- ArgumentMatchers.eq(false),
- ArgumentMatchers.eq(true), /* wasShownHighPriority */
- ArgumentMatchers.eq(assistantFeedbackController),
- ArgumentMatchers.any(MetricsLogger::class.java)
+ any<PackageManager>(),
+ any<INotificationManager>(),
+ eq(onUserInteractionCallback),
+ eq(channelEditorDialogController),
+ eq(statusBarNotification.packageName),
+ any<NotificationChannel>(),
+ eq(entry),
+ any<NotificationInfo.OnSettingsClickListener>(),
+ any<NotificationInfo.OnAppSettingsClickListener>(),
+ any<UiEventLogger>(),
+ eq(true),
+ eq(false),
+ eq(true), /* wasShownHighPriority */
+ eq(assistantFeedbackController),
+ any<MetricsLogger>(),
)
}
@Test
@Throws(Exception::class)
fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
- val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
- val row = Mockito.spy(helper.createRow())
+ val notificationInfoView = mock<NotificationInfo>()
+ val row = spy(helper.createRow())
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
verify(notificationInfoView)
.bindNotification(
- ArgumentMatchers.any(PackageManager::class.java),
- ArgumentMatchers.any(INotificationManager::class.java),
- ArgumentMatchers.eq(onUserInteractionCallback),
- ArgumentMatchers.eq(channelEditorDialogController),
- ArgumentMatchers.eq(statusBarNotification.packageName),
- ArgumentMatchers.any(NotificationChannel::class.java),
- ArgumentMatchers.eq(entry),
- ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
- ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
- ArgumentMatchers.any(UiEventLogger::class.java),
- ArgumentMatchers.eq(true),
- ArgumentMatchers.eq(false),
- ArgumentMatchers.eq(false), /* wasShownHighPriority */
- ArgumentMatchers.eq(assistantFeedbackController),
- ArgumentMatchers.any(MetricsLogger::class.java)
+ any<PackageManager>(),
+ any<INotificationManager>(),
+ eq(onUserInteractionCallback),
+ eq(channelEditorDialogController),
+ eq(statusBarNotification.packageName),
+ any<NotificationChannel>(),
+ eq(entry),
+ any<NotificationInfo.OnSettingsClickListener>(),
+ any<NotificationInfo.OnAppSettingsClickListener>(),
+ any<UiEventLogger>(),
+ eq(true),
+ eq(false),
+ eq(false), /* wasShownHighPriority */
+ eq(assistantFeedbackController),
+ any<MetricsLogger>(),
)
}
@Test
@Throws(Exception::class)
fun testInitializeNotificationInfoView_withInitialAction() {
- val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
- val row = Mockito.spy(helper.createRow())
+ val notificationInfoView = mock<NotificationInfo>()
+ val row = spy(helper.createRow())
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
verify(notificationInfoView)
.bindNotification(
- ArgumentMatchers.any(PackageManager::class.java),
- ArgumentMatchers.any(INotificationManager::class.java),
- ArgumentMatchers.eq(onUserInteractionCallback),
- ArgumentMatchers.eq(channelEditorDialogController),
- ArgumentMatchers.eq(statusBarNotification.packageName),
- ArgumentMatchers.any(NotificationChannel::class.java),
- ArgumentMatchers.eq(entry),
- ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
- ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
- ArgumentMatchers.any(UiEventLogger::class.java),
- ArgumentMatchers.eq(true),
- ArgumentMatchers.eq(false),
- ArgumentMatchers.eq(false), /* wasShownHighPriority */
- ArgumentMatchers.eq(assistantFeedbackController),
- ArgumentMatchers.any(MetricsLogger::class.java)
+ any<PackageManager>(),
+ any<INotificationManager>(),
+ eq(onUserInteractionCallback),
+ eq(channelEditorDialogController),
+ eq(statusBarNotification.packageName),
+ any<NotificationChannel>(),
+ eq(entry),
+ any<NotificationInfo.OnSettingsClickListener>(),
+ any<NotificationInfo.OnAppSettingsClickListener>(),
+ any<UiEventLogger>(),
+ eq(true),
+ eq(false),
+ eq(false), /* wasShownHighPriority */
+ eq(assistantFeedbackController),
+ any<MetricsLogger>(),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
index dcd57f1..c2460f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
@@ -17,30 +17,27 @@
package com.android.systemui.statusbar.policy
import android.app.NotificationManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
-
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
+ return any<T>()
}
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@RunWithLooper()
@SmallTest
class BatteryStateNotifierTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 9bb7607..f91f373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -22,10 +22,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -120,9 +119,9 @@
verify(mBroadcastDispatcher).registerReceiverWithHandler(
brCaptor.capture(),
- anyObject(),
- anyObject(),
- anyObject());
+ any(),
+ any(),
+ any());
mBroadcastReceiver = brCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index ecc7909..3007eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -92,6 +92,7 @@
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
+import com.google.android.msdl.domain.MSDLPlayer;
import com.google.common.collect.ImmutableList;
import dagger.Lazy;
@@ -169,6 +170,9 @@
@Mock
private VibratorHelper mVibratorHelper;
+ @Mock
+ private MSDLPlayer mMSDLPlayer;
+
private int mLongestHideShowAnimationDuration = 250;
private FakeSettings mSecureSettings;
@@ -222,6 +226,7 @@
mDumpManager,
mLazySecureSettings,
mVibratorHelper,
+ mMSDLPlayer,
new FakeSystemClock(),
mVolumeDialogInteractor);
mDialog.init(0, null);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 1b1d8c5..c77d0aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -28,6 +28,7 @@
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardMediaKeyInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -60,6 +61,7 @@
patternViewModelFactory = patternBouncerViewModelFactory,
passwordViewModelFactory = passwordBouncerViewModelFactory,
bouncerHapticPlayer = bouncerHapticPlayer,
+ keyguardMediaKeyInteractor = keyguardMediaKeyInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 2b81da3..fe82ab9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -23,19 +23,24 @@
import com.google.android.msdl.logging.MSDLEvent
class FakeMSDLPlayer : MSDLPlayer {
+ val tokensPlayed = mutableListOf<MSDLToken>()
+ val propertiesPlayed = mutableListOf<InteractionProperties?>()
private val history = arrayListOf<MSDLEvent>()
+
var currentFeedbackLevel = FeedbackLevel.DEFAULT
var latestTokenPlayed: MSDLToken? = null
+ get() = tokensPlayed.lastOrNull()
private set
var latestPropertiesPlayed: InteractionProperties? = null
+ get() = propertiesPlayed.lastOrNull()
private set
override fun getSystemFeedbackLevel(): FeedbackLevel = currentFeedbackLevel
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
- latestTokenPlayed = token
- latestPropertiesPlayed = properties
+ tokensPlayed.add(token)
+ propertiesPlayed.add(properties)
history.add(MSDLEvent(token, properties))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
index 257d758..3fbcf77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/slider/SliderHapticsViewModelFactoryKosmos.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.InteractionSource
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
@@ -40,6 +41,7 @@
sliderHapticFeedbackConfig,
sliderTrackerConfig,
vibratorHelper,
+ msdlPlayer,
fakeSystemClock,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index fbfaba6..c41493e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -41,7 +41,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.settings.displayTracker
-import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
@@ -114,7 +114,7 @@
Kosmos.Fixture {
ShortcutHelperViewModel(
mockRoleManager,
- fakeUserTracker,
+ userTracker,
applicationCoroutineScope,
testDispatcher,
shortcutHelperStateInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index e513e8d..0878649 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -88,9 +88,6 @@
private val _isDreamingWithOverlay = MutableStateFlow(false)
override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
- private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
- override val isActiveDreamLockscreenHosted: StateFlow<Boolean> = _isActiveDreamLockscreenHosted
-
private val _dozeAmount = MutableStateFlow(0f)
override val linearDozeAmount: Flow<Float> = _dozeAmount
@@ -102,8 +99,7 @@
private val _isUdfpsSupported = MutableStateFlow(false)
- private val _isKeyguardGoingAway = MutableStateFlow(false)
- override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+ override val isKeyguardGoingAway = MutableStateFlow(false)
private val _biometricUnlockState =
MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
@@ -169,7 +165,7 @@
}
fun setKeyguardGoingAway(isGoingAway: Boolean) {
- _isKeyguardGoingAway.value = isGoingAway
+ isKeyguardGoingAway.value = isGoingAway
}
fun setKeyguardOccluded(isOccluded: Boolean) {
@@ -235,10 +231,6 @@
_isDreamingWithOverlay.value = isDreaming
}
- override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
- _isActiveDreamLockscreenHosted.value = isLockscreenHosted
- }
-
fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt
new file mode 100644
index 0000000..6f4787b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardMediaKeyInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.volume.data.repository.audioRepository
+
+val Kosmos.keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor by
+ Kosmos.Fixture {
+ KeyguardMediaKeyInteractor(
+ telephonyInteractor = telephonyInteractor,
+ audioRepository = audioRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
index 8b7e5d8..88063c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -18,6 +18,7 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
@@ -31,6 +32,7 @@
falsingManager,
uiEventLogger,
vibratorHelper,
+ msdlPlayer,
systemClock,
activityStarter,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 7f4c670..c3996e40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -85,7 +85,6 @@
import com.android.systemui.util.Assert.runWithCurrentThreadAsMainThread
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.wmshell.BubblesManager
@@ -126,6 +125,7 @@
private val mMainCoroutineContext = mTestScope.coroutineContext
private val mFakeSystemClock = FakeSystemClock()
private val mMainExecutor = FakeExecutor(mFakeSystemClock)
+ private val mDumpManager = DumpManager()
init {
featureFlags.setDefault(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE)
@@ -142,8 +142,7 @@
mGroupMembershipManager = GroupMembershipManagerImpl()
mSmartReplyController = Mockito.mock(SmartReplyController::class.java, STUB_ONLY)
- val dumpManager = DumpManager()
- mGroupExpansionManager = GroupExpansionManagerImpl(dumpManager, mGroupMembershipManager)
+ mGroupExpansionManager = GroupExpansionManagerImpl(mDumpManager, mGroupMembershipManager)
mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java, STUB_ONLY)
mIconManager =
IconManager(
@@ -289,8 +288,8 @@
NotificationOptimizedLinearLayoutFactory(),
{ Mockito.mock(NotificationViewFlipperFactory::class.java) },
NotificationRowIconViewInflaterFactory(
- AppIconProviderImpl(context),
- NotificationIconStyleProviderImpl(),
+ AppIconProviderImpl(context, mDumpManager),
+ NotificationIconStyleProviderImpl(mDumpManager),
),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
index 08c6bba..0fd0f14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row.icon
import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-val Kosmos.appIconProvider by Kosmos.Fixture { AppIconProviderImpl(applicationContext) }
+val Kosmos.appIconProvider by
+ Kosmos.Fixture { AppIconProviderImpl(applicationContext, dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
index 611c90a..0fe84fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row.icon
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-val Kosmos.notificationIconStyleProvider by Kosmos.Fixture { NotificationIconStyleProviderImpl() }
+val Kosmos.notificationIconStyleProvider by
+ Kosmos.Fixture { NotificationIconStyleProviderImpl(dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 6be13be..3219127 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -23,7 +23,7 @@
listeners -= listener
}
- override fun onConfigurationChanged(newConfiguration: Configuration?) {
+ override fun onConfigurationChanged(newConfiguration: Configuration) {
listeners.forEach { it.onConfigChanged(newConfiguration) }
}
@@ -36,7 +36,7 @@
}
fun notifyConfigurationChanged() {
- onConfigurationChanged(newConfiguration = null)
+ onConfigurationChanged(newConfiguration = Configuration())
}
fun notifyLayoutDirectionChanged(isRtl: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
index bca13c6..65247a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
@@ -24,6 +24,6 @@
override fun create(
context: Context,
viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
- statusBarConfigurationController: StatusBarConfigurationController
+ statusBarConfigurationController: StatusBarConfigurationController,
) = FakeStatusBarWindowController()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
index 5cf214a..712ec41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.audioRepository by Kosmos.Fixture { FakeAudioRepository() }
+val Kosmos.fakeAudioRepository by Kosmos.Fixture { FakeAudioRepository() }
+val Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index ba6ffd7..16d2a18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -18,6 +18,7 @@
import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.view.KeyEvent
import com.android.settingslib.volume.data.model.VolumeControllerEvent
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.shared.model.AudioStream
@@ -61,6 +62,15 @@
val isInitialized: Boolean
get() = mutableIsInitialized
+ private val _dispatchedKeyEvents = mutableListOf<KeyEvent>()
+
+ val dispatchedKeyEvents: List<KeyEvent>
+ get() {
+ val currentValue = _dispatchedKeyEvents.toList()
+ _dispatchedKeyEvents.clear()
+ return currentValue
+ }
+
override fun init() {
mutableIsInitialized = true
}
@@ -145,4 +155,8 @@
mutableIsVolumeControllerVisible.value = isVisible
}
}
+
+ override fun dispatchMediaKeyEvent(event: KeyEvent) {
+ _dispatchedKeyEvents.add(event)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
new file mode 100644
index 0000000..c2a1544
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.volume.dialog.ringer.domain
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+
+val Kosmos.volumeDialogRingerInteractor by
+ Kosmos.Fixture {
+ VolumeDialogRingerInteractor(
+ coroutineScope = applicationCoroutineScope,
+ volumeDialogStateInteractor = volumeDialogStateInteractor,
+ controller = volumeDialogController,
+ )
+ }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d918201..ff2abd2 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -100,6 +100,9 @@
srcs: [
"runtime-helper-src/libcore-fake/**/*.java",
],
+ libs: [
+ "app-compat-annotations",
+ ],
static_libs: [
"ravenwood-runtime-common",
],
@@ -121,6 +124,7 @@
],
static_libs: [
"ravenwood-runtime-common",
+ "androidx.annotation_annotation",
],
libs: [
"framework-minus-apex.ravenwood",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
index 3535cb2..870a10a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -42,6 +42,10 @@
private final RavenwoodConfig mConfig;
+ // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by
+ // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry?
+ RavenwoodContext mSystemServerContext;
+
public RavenwoodConfigState(RavenwoodConfig config) {
mConfig = config;
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 9a145cb..c2806da 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,6 +16,8 @@
package android.platform.test.ravenwood;
+import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;
+
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -267,6 +269,13 @@
config.mInstContext = instContext;
config.mTargetContext = targetContext;
+ final Supplier<Resources> systemResourcesLoader = () -> {
+ return config.mState.loadResources(null);
+ };
+
+ config.mState.mSystemServerContext =
+ new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
+
// Prepare other fields.
config.mInstrumentation = new Instrumentation();
config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
@@ -314,6 +323,9 @@
((RavenwoodContext) config.mTargetContext).cleanUp();
config.mTargetContext = null;
}
+ if (config.mState.mSystemServerContext != null) {
+ config.mState.mSystemServerContext.cleanUp();
+ }
Looper.getMainLooper().quit();
Looper.clearMainLooperForTest();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index 3946dd84..f198a08 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -33,6 +33,9 @@
import java.util.Set;
public class RavenwoodSystemServer {
+
+ static final String ANDROID_PACKAGE_NAME = "android";
+
/**
* Set of services that we know how to provide under Ravenwood. We keep this set distinct
* from {@code com.android.server.SystemServer} to give us the ability to choose either
@@ -67,7 +70,7 @@
sStartedServices = new ArraySet<>();
sTimings = new TimingsTraceAndSlog();
- sServiceManager = new SystemServiceManager(config.mInstContext);
+ sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext);
sServiceManager.setStartInfo(false,
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 1f6e11d..37b0abc 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -67,6 +67,7 @@
String mTargetPackageName;
int mMinSdkLevel;
+ int mTargetSdkLevel;
boolean mProvideMainThread = false;
@@ -150,6 +151,14 @@
}
/**
+ * Configure the target SDK level of the test.
+ */
+ public Builder setTargetSdkLevel(int sdkLevel) {
+ mConfig.mTargetSdkLevel = sdkLevel;
+ return this;
+ }
+
+ /**
* Configure a "main" thread to be available for the duration of the test, as defined
* by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
*
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index ced1519..9bc45be 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -146,6 +146,9 @@
if (root.startsWith("soc.")) return true;
if (root.startsWith("system.")) return true;
+ // For PropertyInvalidatedCache
+ if (root.startsWith("cache_key.")) return true;
+
switch (key) {
case "gsm.version.baseband":
case "no.such.thing":
@@ -170,6 +173,9 @@
if (root.startsWith("debug.")) return true;
+ // For PropertyInvalidatedCache
+ if (root.startsWith("cache_key.")) return true;
+
return mKeyWritable.contains(key);
}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java
new file mode 100644
index 0000000..1e3b3fc
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java
@@ -0,0 +1,1035 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+// [ravenwood] This is an exact copy from StatsD, until we make StatsD available on Ravenwood.
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Build;
+import android.os.SystemClock;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * StatsEvent builds and stores the buffer sent over the statsd socket.
+ * This class defines and encapsulates the socket protocol.
+ *
+ * <p>Usage:</p>
+ * <pre>
+ * // Pushed event
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeBoolean(false)
+ * .writeString("annotated String field")
+ * .addBooleanAnnotation(annotationId, true)
+ * .usePooledBuffer()
+ * .build();
+ * StatsLog.write(statsEvent);
+ *
+ * // Pulled event
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeBoolean(false)
+ * .writeString("annotated String field")
+ * .addBooleanAnnotation(annotationId, true)
+ * .build();
+ * </pre>
+ * @hide
+ **/
+@SystemApi
+public final class StatsEvent {
+ // Type Ids.
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_INT = 0x00;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_LONG = 0x01;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_STRING = 0x02;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_LIST = 0x03;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_FLOAT = 0x04;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_BOOLEAN = 0x05;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_BYTE_ARRAY = 0x06;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_OBJECT = 0x07;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final byte TYPE_ERRORS = 0x0F;
+
+ // Error flags.
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_NO_TIMESTAMP = 0x1;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_NO_ATOM_ID = 0x2;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_OVERFLOW = 0x4;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_TOO_MANY_FIELDS = 0x200;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting public static final int ERROR_LIST_TOO_LONG = 0x4000;
+
+ // Size limits.
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int MAX_ANNOTATION_COUNT = 15;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int MAX_ATTRIBUTION_NODES = 127;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int MAX_NUM_ELEMENTS = 127;
+
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static final int MAX_KEY_VALUE_PAIRS = 127;
+
+ private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
+
+ // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
+ // See android_util_StatsLog.cpp.
+ private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+
+ private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
+
+ private final int mAtomId;
+ private final byte[] mPayload;
+ private Buffer mBuffer;
+ private final int mNumBytes;
+
+ private StatsEvent(final int atomId, @Nullable final Buffer buffer,
+ @NonNull final byte[] payload, final int numBytes) {
+ mAtomId = atomId;
+ mBuffer = buffer;
+ mPayload = payload;
+ mNumBytes = numBytes;
+ }
+
+ /**
+ * Returns a new StatsEvent.Builder for building StatsEvent object.
+ **/
+ @NonNull
+ public static Builder newBuilder() {
+ return new Builder(Buffer.obtain());
+ }
+
+ /**
+ * Get the atom Id of the atom encoded in this StatsEvent object.
+ *
+ * @hide
+ **/
+ public int getAtomId() {
+ return mAtomId;
+ }
+
+ /**
+ * Get the byte array that contains the encoded payload that can be sent to statsd.
+ *
+ * @hide
+ **/
+ @NonNull
+ public byte[] getBytes() {
+ return mPayload;
+ }
+
+ /**
+ * Get the number of bytes used to encode the StatsEvent payload.
+ *
+ * @hide
+ **/
+ public int getNumBytes() {
+ return mNumBytes;
+ }
+
+ /**
+ * Recycle resources used by this StatsEvent object.
+ * No actions should be taken on this StatsEvent after release() is called.
+ *
+ * @hide
+ **/
+ public void release() {
+ if (mBuffer != null) {
+ mBuffer.release();
+ mBuffer = null;
+ }
+ }
+
+ /**
+ * Builder for constructing a StatsEvent object.
+ *
+ * <p>This class defines and encapsulates the socket encoding for the
+ *buffer. The write methods must be called in the same order as the order of
+ *fields in the atom definition.</p>
+ *
+ * <p>setAtomId() must be called immediately after
+ *StatsEvent.newBuilder().</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * // Atom definition.
+ * message MyAtom {
+ * optional int32 field1 = 1;
+ * optional int64 field2 = 2;
+ * optional string field3 = 3 [(annotation1) = true];
+ * optional repeated int32 field4 = 4;
+ * }
+ *
+ * // StatsEvent construction for pushed event.
+ * StatsEvent.newBuilder()
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeInt(3) // field1
+ * .writeLong(8L) // field2
+ * .writeString("foo") // field 3
+ * .addBooleanAnnotation(annotation1Id, true)
+ * .writeIntArray({ 1, 2, 3 });
+ * .usePooledBuffer()
+ * .build();
+ *
+ * // StatsEvent construction for pulled event.
+ * StatsEvent.newBuilder()
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeInt(3) // field1
+ * .writeLong(8L) // field2
+ * .writeString("foo") // field 3
+ * .addBooleanAnnotation(annotation1Id, true)
+ * .writeIntArray({ 1, 2, 3 });
+ * .build();
+ * </pre>
+ **/
+ public static final class Builder {
+ // Fixed positions.
+ private static final int POS_NUM_ELEMENTS = 1;
+ private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
+ private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
+
+ private final Buffer mBuffer;
+ private long mTimestampNs;
+ private int mAtomId;
+ private byte mCurrentAnnotationCount;
+ private int mPos;
+ private int mPosLastField;
+ private byte mLastType;
+ private int mNumElements;
+ private int mErrorMask;
+ private boolean mUsePooledBuffer = false;
+
+ private Builder(final Buffer buffer) {
+ mBuffer = buffer;
+ mCurrentAnnotationCount = 0;
+ mAtomId = 0;
+ mTimestampNs = SystemClock.elapsedRealtimeNanos();
+ mNumElements = 0;
+
+ // Set mPos to 0 for writing TYPE_OBJECT at 0th position.
+ mPos = 0;
+ writeTypeId(TYPE_OBJECT);
+
+ // Write timestamp.
+ mPos = POS_TIMESTAMP_NS;
+ writeLong(mTimestampNs);
+ }
+
+ /**
+ * Sets the atom id for this StatsEvent.
+ *
+ * This should be called immediately after StatsEvent.newBuilder()
+ * and should only be called once.
+ * Not calling setAtomId will result in ERROR_NO_ATOM_ID.
+ * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
+ **/
+ @NonNull
+ public Builder setAtomId(final int atomId) {
+ if (0 == mAtomId) {
+ mAtomId = atomId;
+
+ if (1 == mNumElements) { // Only timestamp is written so far.
+ writeInt(atomId);
+ } else {
+ // setAtomId called out of order.
+ mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Write a boolean field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeBoolean(final boolean value) {
+ // Write boolean typeId byte followed by boolean byte representation.
+ writeTypeId(TYPE_BOOLEAN);
+ mPos += mBuffer.putBoolean(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write an integer field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeInt(final int value) {
+ // Write integer typeId byte followed by 4-byte representation of value.
+ writeTypeId(TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a long field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeLong(final long value) {
+ // Write long typeId byte followed by 8-byte representation of value.
+ writeTypeId(TYPE_LONG);
+ mPos += mBuffer.putLong(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a float field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeFloat(final float value) {
+ // Write float typeId byte followed by 4-byte representation of value.
+ writeTypeId(TYPE_FLOAT);
+ mPos += mBuffer.putFloat(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a String field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeString(@NonNull final String value) {
+ // Write String typeId byte, followed by 4-byte representation of number of bytes
+ // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
+ final byte[] valueBytes = stringToBytes(value);
+ writeByteArray(valueBytes, TYPE_STRING);
+ return this;
+ }
+
+ /**
+ * Write a byte array field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeByteArray(@NonNull final byte[] value) {
+ // Write byte array typeId byte, followed by 4-byte representation of number of bytes
+ // in value, followed by the actual byte array.
+ writeByteArray(value, TYPE_BYTE_ARRAY);
+ return this;
+ }
+
+ private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
+ writeTypeId(typeId);
+ final int numBytes = value.length;
+ mPos += mBuffer.putInt(mPos, numBytes);
+ mPos += mBuffer.putByteArray(mPos, value);
+ mNumElements++;
+ }
+
+ /**
+ * Write an attribution chain field to this StatsEvent.
+ *
+ * The sizes of uids and tags must be equal. The AttributionNode at position i is
+ * made up of uids[i] and tags[i].
+ *
+ * @param uids array of uids in the attribution nodes.
+ * @param tags array of tags in the attribution nodes.
+ **/
+ @NonNull
+ public Builder writeAttributionChain(
+ @NonNull final int[] uids, @NonNull final String[] tags) {
+ final byte numUids = (byte) uids.length;
+ final byte numTags = (byte) tags.length;
+
+ if (numUids != numTags) {
+ mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
+ } else if (numUids > MAX_ATTRIBUTION_NODES) {
+ mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+ } else {
+ // Write attribution chain typeId byte, followed by 1-byte representation of
+ // number of attribution nodes, followed by encoding of each attribution node.
+ writeTypeId(TYPE_ATTRIBUTION_CHAIN);
+ mPos += mBuffer.putByte(mPos, numUids);
+ for (int i = 0; i < numUids; i++) {
+ // Each uid is encoded as 4-byte representation of its int value.
+ mPos += mBuffer.putInt(mPos, uids[i]);
+
+ // Each tag is encoded as 4-byte representation of number of bytes in its
+ // UTF-8 encoding, followed by the actual UTF-8 bytes.
+ final byte[] tagBytes = stringToBytes(tags[i]);
+ mPos += mBuffer.putInt(mPos, tagBytes.length);
+ mPos += mBuffer.putByteArray(mPos, tagBytes);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write KeyValuePairsAtom entries to this StatsEvent.
+ *
+ * @param intMap Integer key-value pairs.
+ * @param longMap Long key-value pairs.
+ * @param stringMap String key-value pairs.
+ * @param floatMap Float key-value pairs.
+ **/
+ @NonNull
+ public Builder writeKeyValuePairs(
+ @Nullable final SparseIntArray intMap,
+ @Nullable final SparseLongArray longMap,
+ @Nullable final SparseArray<String> stringMap,
+ @Nullable final SparseArray<Float> floatMap) {
+ final int intMapSize = null == intMap ? 0 : intMap.size();
+ final int longMapSize = null == longMap ? 0 : longMap.size();
+ final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+ final int floatMapSize = null == floatMap ? 0 : floatMap.size();
+ final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
+
+ if (totalCount > MAX_KEY_VALUE_PAIRS) {
+ mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+ } else {
+ writeTypeId(TYPE_KEY_VALUE_PAIRS);
+ mPos += mBuffer.putByte(mPos, (byte) totalCount);
+
+ for (int i = 0; i < intMapSize; i++) {
+ final int key = intMap.keyAt(i);
+ final int value = intMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ }
+
+ for (int i = 0; i < longMapSize; i++) {
+ final int key = longMap.keyAt(i);
+ final long value = longMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_LONG);
+ mPos += mBuffer.putLong(mPos, value);
+ }
+
+ for (int i = 0; i < stringMapSize; i++) {
+ final int key = stringMap.keyAt(i);
+ final String value = stringMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_STRING);
+ final byte[] valueBytes = stringToBytes(value);
+ mPos += mBuffer.putInt(mPos, valueBytes.length);
+ mPos += mBuffer.putByteArray(mPos, valueBytes);
+ }
+
+ for (int i = 0; i < floatMapSize; i++) {
+ final int key = floatMap.keyAt(i);
+ final float value = floatMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_FLOAT);
+ mPos += mBuffer.putFloat(mPos, value);
+ }
+
+ mNumElements++;
+ }
+
+ return this;
+ }
+
+ /**
+ * Write a repeated boolean field to this StatsEvent.
+ *
+ * The list size must not exceed 127. Otherwise, the array isn't written
+ * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+ * StatsEvent errors field.
+ *
+ * @param elements array of booleans.
+ **/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public Builder writeBooleanArray(@NonNull final boolean[] elements) {
+ final byte numElements = (byte)elements.length;
+
+ if (writeArrayInfo(numElements, TYPE_BOOLEAN)) {
+ // Write encoding of each element.
+ for (int i = 0; i < numElements; i++) {
+ mPos += mBuffer.putBoolean(mPos, elements[i]);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write a repeated int field to this StatsEvent.
+ *
+ * The list size must not exceed 127. Otherwise, the array isn't written
+ * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+ * StatsEvent errors field.
+ *
+ * @param elements array of ints.
+ **/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public Builder writeIntArray(@NonNull final int[] elements) {
+ final byte numElements = (byte)elements.length;
+
+ if (writeArrayInfo(numElements, TYPE_INT)) {
+ // Write encoding of each element.
+ for (int i = 0; i < numElements; i++) {
+ mPos += mBuffer.putInt(mPos, elements[i]);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write a repeated long field to this StatsEvent.
+ *
+ * The list size must not exceed 127. Otherwise, the array isn't written
+ * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+ * StatsEvent errors field.
+ *
+ * @param elements array of longs.
+ **/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public Builder writeLongArray(@NonNull final long[] elements) {
+ final byte numElements = (byte)elements.length;
+
+ if (writeArrayInfo(numElements, TYPE_LONG)) {
+ // Write encoding of each element.
+ for (int i = 0; i < numElements; i++) {
+ mPos += mBuffer.putLong(mPos, elements[i]);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write a repeated float field to this StatsEvent.
+ *
+ * The list size must not exceed 127. Otherwise, the array isn't written
+ * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+ * StatsEvent errors field.
+ *
+ * @param elements array of floats.
+ **/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public Builder writeFloatArray(@NonNull final float[] elements) {
+ final byte numElements = (byte)elements.length;
+
+ if (writeArrayInfo(numElements, TYPE_FLOAT)) {
+ // Write encoding of each element.
+ for (int i = 0; i < numElements; i++) {
+ mPos += mBuffer.putFloat(mPos, elements[i]);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write a repeated string field to this StatsEvent.
+ *
+ * The list size must not exceed 127. Otherwise, the array isn't written
+ * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
+ * StatsEvent errors field.
+ *
+ * @param elements array of strings.
+ **/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public Builder writeStringArray(@NonNull final String[] elements) {
+ final byte numElements = (byte)elements.length;
+
+ if (writeArrayInfo(numElements, TYPE_STRING)) {
+ // Write encoding of each element.
+ for (int i = 0; i < numElements; i++) {
+ final byte[] elementBytes = stringToBytes(elements[i]);
+ mPos += mBuffer.putInt(mPos, elementBytes.length);
+ mPos += mBuffer.putByteArray(mPos, elementBytes);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write a boolean annotation for the last field written.
+ **/
+ @NonNull
+ public Builder addBooleanAnnotation(
+ final byte annotationId, final boolean value) {
+ // Ensure there's a field written to annotate.
+ if (mNumElements < 2) {
+ mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+ mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+ } else {
+ mPos += mBuffer.putByte(mPos, annotationId);
+ mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
+ mPos += mBuffer.putBoolean(mPos, value);
+ mCurrentAnnotationCount++;
+ writeAnnotationCount();
+ }
+
+ return this;
+ }
+
+ /**
+ * Write an integer annotation for the last field written.
+ **/
+ @NonNull
+ public Builder addIntAnnotation(final byte annotationId, final int value) {
+ if (mNumElements < 2) {
+ mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+ mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+ } else {
+ mPos += mBuffer.putByte(mPos, annotationId);
+ mPos += mBuffer.putByte(mPos, TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ mCurrentAnnotationCount++;
+ writeAnnotationCount();
+ }
+
+ return this;
+ }
+
+ /**
+ * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
+ * This should be called for pushed events to reduce memory allocations and garbage
+ * collections.
+ **/
+ @NonNull
+ public Builder usePooledBuffer() {
+ mUsePooledBuffer = true;
+ mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
+ return this;
+ }
+
+ /**
+ * Builds a StatsEvent object with values entered in this Builder.
+ **/
+ @NonNull
+ public StatsEvent build() {
+ if (0L == mTimestampNs) {
+ mErrorMask |= ERROR_NO_TIMESTAMP;
+ }
+ if (0 == mAtomId) {
+ mErrorMask |= ERROR_NO_ATOM_ID;
+ }
+ if (mBuffer.hasOverflowed()) {
+ mErrorMask |= ERROR_OVERFLOW;
+ }
+ if (mNumElements > MAX_NUM_ELEMENTS) {
+ mErrorMask |= ERROR_TOO_MANY_FIELDS;
+ }
+
+ if (0 == mErrorMask) {
+ mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
+ } else {
+ // Write atom id and error mask. Overwrite any annotations for atom Id.
+ mPos = POS_ATOM_ID;
+ mPos += mBuffer.putByte(mPos, TYPE_INT);
+ mPos += mBuffer.putInt(mPos, mAtomId);
+ mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
+ mPos += mBuffer.putInt(mPos, mErrorMask);
+ mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
+ }
+
+ final int size = mPos;
+
+ if (mUsePooledBuffer) {
+ return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
+ } else {
+ // Create a copy of the buffer with the required number of bytes.
+ final byte[] payload = new byte[size];
+ System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
+
+ // Return Buffer instance to the pool.
+ mBuffer.release();
+
+ return new StatsEvent(mAtomId, null, payload, size);
+ }
+ }
+
+ private void writeTypeId(final byte typeId) {
+ mPosLastField = mPos;
+ mLastType = typeId;
+ mCurrentAnnotationCount = 0;
+ final byte encodedId = (byte) (typeId & 0x0F);
+ mPos += mBuffer.putByte(mPos, encodedId);
+ }
+
+ private void writeAnnotationCount() {
+ // Use first 4 bits for annotation count and last 4 bits for typeId.
+ final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
+ mBuffer.putByte(mPosLastField, encodedId);
+ }
+
+ @NonNull
+ private static byte[] stringToBytes(@Nullable final String value) {
+ return (null == value ? "" : value).getBytes(UTF_8);
+ }
+
+ private boolean writeArrayInfo(final byte numElements,
+ final byte elementTypeId) {
+ if (numElements > MAX_NUM_ELEMENTS) {
+ mErrorMask |= ERROR_LIST_TOO_LONG;
+ return false;
+ }
+ // Write list typeId byte, 1-byte representation of number of
+ // elements, and element typeId byte.
+ writeTypeId(TYPE_LIST);
+ mPos += mBuffer.putByte(mPos, numElements);
+ // Write element typeId byte without setting mPosLastField and mLastType (i.e. don't use
+ // #writeTypeId)
+ final byte encodedId = (byte) (elementTypeId & 0x0F);
+ mPos += mBuffer.putByte(mPos, encodedId);
+ return true;
+ }
+ }
+
+ private static final class Buffer {
+ private static Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static Buffer sPool;
+
+ private byte[] mBytes;
+ private boolean mOverflow = false;
+ private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
+
+ @NonNull
+ private static Buffer obtain() {
+ final Buffer buffer;
+ synchronized (sLock) {
+ buffer = null == sPool ? new Buffer() : sPool;
+ sPool = null;
+ }
+ buffer.reset();
+ return buffer;
+ }
+
+ private Buffer() {
+ final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(MAX_PUSH_PAYLOAD_SIZE);
+ mBytes = tempBuffer.hasArray() ? tempBuffer.array() : new byte [MAX_PUSH_PAYLOAD_SIZE];
+ }
+
+ @NonNull
+ private byte[] getBytes() {
+ return mBytes;
+ }
+
+ private void release() {
+ // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
+ if (mMaxSize <= MAX_PUSH_PAYLOAD_SIZE) {
+ synchronized (sLock) {
+ if (null == sPool) {
+ sPool = this;
+ }
+ }
+ }
+ }
+
+ private void reset() {
+ mOverflow = false;
+ mMaxSize = MAX_PULL_PAYLOAD_SIZE;
+ }
+
+ private void setMaxSize(final int maxSize, final int numBytesWritten) {
+ mMaxSize = maxSize;
+ if (numBytesWritten > maxSize) {
+ mOverflow = true;
+ }
+ }
+
+ private boolean hasOverflowed() {
+ return mOverflow;
+ }
+
+ /**
+ * Checks for available space in the byte array.
+ *
+ * @param index starting position in the buffer to start the check.
+ * @param numBytes number of bytes to check from index.
+ * @return true if space is available, false otherwise.
+ **/
+ private boolean hasEnoughSpace(final int index, final int numBytes) {
+ final int totalBytesNeeded = index + numBytes;
+
+ if (totalBytesNeeded > mMaxSize) {
+ mOverflow = true;
+ return false;
+ }
+
+ // Expand buffer if needed.
+ if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
+ int newSize = mBytes.length;
+ do {
+ newSize *= 2;
+ } while (newSize <= totalBytesNeeded);
+
+ if (newSize > mMaxSize) {
+ newSize = mMaxSize;
+ }
+
+ mBytes = Arrays.copyOf(mBytes, newSize);
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes a byte into the buffer.
+ *
+ * @param index position in the buffer where the byte is written.
+ * @param value the byte to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putByte(final int index, final byte value) {
+ if (hasEnoughSpace(index, Byte.BYTES)) {
+ mBytes[index] = (byte) (value);
+ return Byte.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a boolean into the buffer.
+ *
+ * @param index position in the buffer where the boolean is written.
+ * @param value the boolean to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putBoolean(final int index, final boolean value) {
+ return putByte(index, (byte) (value ? 1 : 0));
+ }
+
+ /**
+ * Writes an integer into the buffer.
+ *
+ * @param index position in the buffer where the integer is written.
+ * @param value the integer to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putInt(final int index, final int value) {
+ if (hasEnoughSpace(index, Integer.BYTES)) {
+ // Use little endian byte order.
+ mBytes[index] = (byte) (value);
+ mBytes[index + 1] = (byte) (value >> 8);
+ mBytes[index + 2] = (byte) (value >> 16);
+ mBytes[index + 3] = (byte) (value >> 24);
+ return Integer.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a long into the buffer.
+ *
+ * @param index position in the buffer where the long is written.
+ * @param value the long to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putLong(final int index, final long value) {
+ if (hasEnoughSpace(index, Long.BYTES)) {
+ // Use little endian byte order.
+ mBytes[index] = (byte) (value);
+ mBytes[index + 1] = (byte) (value >> 8);
+ mBytes[index + 2] = (byte) (value >> 16);
+ mBytes[index + 3] = (byte) (value >> 24);
+ mBytes[index + 4] = (byte) (value >> 32);
+ mBytes[index + 5] = (byte) (value >> 40);
+ mBytes[index + 6] = (byte) (value >> 48);
+ mBytes[index + 7] = (byte) (value >> 56);
+ return Long.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a float into the buffer.
+ *
+ * @param index position in the buffer where the float is written.
+ * @param value the float to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putFloat(final int index, final float value) {
+ return putInt(index, Float.floatToIntBits(value));
+ }
+
+ /**
+ * Copies a byte array into the buffer.
+ *
+ * @param index position in the buffer where the byte array is copied.
+ * @param value the byte array to copy.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putByteArray(final int index, @NonNull final byte[] value) {
+ final int numBytes = value.length;
+ if (hasEnoughSpace(index, numBytes)) {
+ System.arraycopy(value, 0, mBytes, index, numBytes);
+ return numBytes;
+ }
+ return 0;
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java
new file mode 100644
index 0000000..c1c20cf
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/*
+ * [Ravenwood] This is copied from StatsD, with the following changes:
+ * - The static {} is commented out.
+ * - All references to IStatsD and StatsdStatsLog are commented out.
+ * - The native method is no-oped.
+ */
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Build;
+//import android.os.IStatsd;
+import android.os.Process;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.annotation.RequiresApi;
+
+//import com.android.internal.statsd.StatsdStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * StatsLog provides an API for developers to send events to statsd. The events can be used to
+ * define custom metrics in side statsd.
+ */
+public final class StatsLog {
+
+// // Load JNI library
+// static {
+// System.loadLibrary("stats_jni");
+// }
+ private static final String TAG = "StatsLog";
+ private static final boolean DEBUG = false;
+ private static final int EXPERIMENT_IDS_FIELD_ID = 1;
+
+ /**
+ * Annotation ID constant for logging UID field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_IS_UID = 1;
+
+ /**
+ * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+
+ /**
+ * Annotation ID constant for a state atom's primary field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3;
+
+ /**
+ * Annotation ID constant for state atom's state field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4;
+
+ /**
+ * Annotation ID constant to indicate the first UID in the attribution chain
+ * is a primary field.
+ * Should only be used for attribution chain fields.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
+
+ /**
+ * Annotation ID constant to indicate which state is default for the state atom.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_DEFAULT_STATE = 6;
+
+ /**
+ * Annotation ID constant to signal all states should be reset to the default state.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
+
+ /**
+ * Annotation ID constant to indicate state changes need to account for nesting.
+ * This should only be used with binary state atoms.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ public static final byte ANNOTATION_ID_STATE_NESTED = 8;
+
+ /**
+ * Annotation ID constant to indicate the restriction category of an atom.
+ * This annotation must only be attached to the atom id. This is an int annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains peripheral device info.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app usage information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app activity information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains health connect information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains accessibility information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains system search information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains user engagement information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains demographic classification
+ * information. This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18;
+
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_CATEGORY_" }, value = {
+ RESTRICTION_CATEGORY_DIAGNOSTIC,
+ RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+ RESTRICTION_CATEGORY_AUTHENTICATION,
+ RESTRICTION_CATEGORY_FRAUD_AND_ABUSE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionCategory {}
+
+ /**
+ * Restriction category for atoms about diagnostics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1;
+
+ /**
+ * Restriction category for atoms about system intelligence.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2;
+
+ /**
+ * Restriction category for atoms about authentication.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3;
+
+ /**
+ * Restriction category for atoms about fraud and abuse.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4;
+
+ private StatsLog() {
+ }
+
+ /**
+ * Logs a start event.
+ *
+ * @param label developer-chosen label.
+ * @return True if the log request was sent to statsd.
+ */
+ public static boolean logStart(int label) {
+ int callingUid = Process.myUid();
+// StatsdStatsLog.write(
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+// callingUid,
+// label,
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+ return true;
+ }
+
+ /**
+ * Logs a stop event.
+ *
+ * @param label developer-chosen label.
+ * @return True if the log request was sent to statsd.
+ */
+ public static boolean logStop(int label) {
+ int callingUid = Process.myUid();
+// StatsdStatsLog.write(
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+// callingUid,
+// label,
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+ return true;
+ }
+
+ /**
+ * Logs an event that does not represent a start or stop boundary.
+ *
+ * @param label developer-chosen label.
+ * @return True if the log request was sent to statsd.
+ */
+ public static boolean logEvent(int label) {
+ int callingUid = Process.myUid();
+// StatsdStatsLog.write(
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+// callingUid,
+// label,
+// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+ return true;
+ }
+
+ /**
+ * Logs an event for binary push for module updates.
+ *
+ * @param trainName name of install train.
+ * @param trainVersionCode version code of the train.
+ * @param options optional flags about this install.
+ * The last 3 bits indicate options:
+ * 0x01: FLAG_REQUIRE_STAGING
+ * 0x02: FLAG_ROLLBACK_ENABLED
+ * 0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR
+ * @param state current install state. Defined as State enums in
+ * BinaryPushStateChanged atom in
+ * frameworks/proto_logging/stats/atoms.proto
+ * @param experimentIds experiment ids.
+ * @return True if the log request was sent to statsd.
+ */
+ @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
+ public static boolean logBinaryPushStateChanged(@NonNull String trainName,
+ long trainVersionCode, int options, int state,
+ @NonNull long[] experimentIds) {
+ ProtoOutputStream proto = new ProtoOutputStream();
+ for (long id : experimentIds) {
+ proto.write(
+ ProtoOutputStream.FIELD_TYPE_INT64
+ | ProtoOutputStream.FIELD_COUNT_REPEATED
+ | EXPERIMENT_IDS_FIELD_ID,
+ id);
+ }
+// StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED,
+// trainName,
+// trainVersionCode,
+// (options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
+// (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
+// (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
+// state,
+// proto.getBytes(),
+// 0,
+// 0,
+// false);
+ return true;
+ }
+
+ /**
+ * Write an event to stats log using the raw format.
+ *
+ * @param buffer The encoded buffer of data to write.
+ * @param size The number of bytes from the buffer to write.
+ * @hide
+ * @deprecated Use {@link write(final StatsEvent statsEvent)} instead.
+ *
+ */
+ @Deprecated
+ @SystemApi
+ public static void writeRaw(@NonNull byte[] buffer, int size) {
+ writeImpl(buffer, size, 0);
+ }
+
+ /**
+ * Write an event to stats log using the raw format.
+ *
+ * @param buffer The encoded buffer of data to write.
+ * @param size The number of bytes from the buffer to write.
+ * @param atomId The id of the atom to which the event belongs.
+ */
+// private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId);
+ private static void writeImpl(@NonNull byte[] buffer, int size, int atomId) {
+ // no-op for now
+ }
+
+ /**
+ * Write an event to stats log using the raw format encapsulated in StatsEvent.
+ * After writing to stats log, release() is called on the StatsEvent object.
+ * No further action should be taken on the StatsEvent object following this call.
+ *
+ * @param statsEvent The StatsEvent object containing the encoded buffer of data to write.
+ * @hide
+ */
+ @SystemApi
+ public static void write(@NonNull final StatsEvent statsEvent) {
+ writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
+ statsEvent.release();
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java
new file mode 100644
index 0000000..c737684
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compat;
+
+// [Ravenwood] Copied from libcore, with "RAVENWOOD-CHANGE"
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+
+import libcore.api.IntraCoreApi;
+import libcore.util.NonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Internal APIs for logging and gating compatibility changes.
+ *
+ * @see ChangeId
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+@IntraCoreApi
+public final class Compatibility {
+
+ private Compatibility() {}
+
+ /**
+ * Reports that a compatibility change is affecting the current process now.
+ *
+ * <p>Calls to this method from a non-app process are ignored. This allows code implementing
+ * APIs that are used by apps and by other code (e.g. the system server) to report changes
+ * regardless of the process it's running in. When called in a non-app process, this method is
+ * a no-op.
+ *
+ * <p>Note: for changes that are gated using {@link #isChangeEnabled(long)}, you do not need to
+ * call this API directly. The change will be reported for you in the case that
+ * {@link #isChangeEnabled(long)} returns {@code true}.
+ *
+ * @param changeId The ID of the compatibility change taking effect.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public static void reportUnconditionalChange(@ChangeId long changeId) {
+ sCallbacks.onChangeReported(changeId);
+ }
+
+ /**
+ * Query if a given compatibility change is enabled for the current process. This method should
+ * only be called by code running inside a process of the affected app.
+ *
+ * <p>If this method returns {@code true}, the calling code should implement the compatibility
+ * change, resulting in differing behaviour compared to earlier releases. If this method returns
+ * {@code false}, the calling code should behave as it did in earlier releases.
+ *
+ * <p>When this method returns {@code true}, it will also report the change as
+ * {@link #reportUnconditionalChange(long)} would, so there is no need to call that method
+ * directly.
+ *
+ * @param changeId The ID of the compatibility change in question.
+ * @return {@code true} if the change is enabled for the current app.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public static boolean isChangeEnabled(@ChangeId long changeId) {
+ return sCallbacks.isChangeEnabled(changeId);
+ }
+
+ private static final BehaviorChangeDelegate DEFAULT_CALLBACKS = new BehaviorChangeDelegate(){};
+
+ private volatile static BehaviorChangeDelegate sCallbacks = DEFAULT_CALLBACKS;
+
+ /**
+ * Sets the behavior change delegate.
+ *
+ * All changes reported via the {@link Compatibility} class will be forwarded to this class.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks) {
+ sCallbacks = Objects.requireNonNull(callbacks);
+ }
+
+ /**
+ * Removes a behavior change delegate previously set via {@link #setBehaviorChangeDelegate}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void clearBehaviorChangeDelegate() {
+ sCallbacks = DEFAULT_CALLBACKS;
+ }
+
+ /**
+ * Return the behavior change delegate
+ *
+ * @hide
+ */
+ // VisibleForTesting
+ @NonNull
+ public static BehaviorChangeDelegate getBehaviorChangeDelegate() {
+ return sCallbacks;
+ }
+
+ /**
+ * For use by tests only. Causes values from {@code overrides} to be returned instead of the
+ * real value.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setOverrides(ChangeConfig overrides) {
+ // Setting overrides twice in a row does not need to be supported because
+ // this method is only for enabling/disabling changes for the duration of
+ // a single test.
+ // In production, the app is restarted when changes get enabled or disabled,
+ // and the ChangeConfig is then set exactly once on that app process.
+ if (sCallbacks instanceof OverrideCallbacks) {
+ throw new IllegalStateException("setOverrides has already been called!");
+ }
+ sCallbacks = new OverrideCallbacks(sCallbacks, overrides);
+ }
+
+ /**
+ * For use by tests only. Removes overrides set by {@link #setOverrides}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void clearOverrides() {
+ if (!(sCallbacks instanceof OverrideCallbacks)) {
+ throw new IllegalStateException("No overrides set");
+ }
+ sCallbacks = ((OverrideCallbacks) sCallbacks).delegate;
+ }
+
+ /**
+ * Base class for compatibility API implementations. The default implementation logs a warning
+ * to logcat.
+ *
+ * This is provided as a class rather than an interface to allow new methods to be added without
+ * breaking @SystemApi binary compatibility.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public interface BehaviorChangeDelegate {
+ /**
+ * Called when a change is reported via {@link Compatibility#reportUnconditionalChange}
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ default void onChangeReported(long changeId) {
+ // Do not use String.format here (b/160912695)
+
+ // RAVENWOOD-CHANGE
+ System.out.println("No Compatibility callbacks set! Reporting change " + changeId);
+ }
+
+ /**
+ * Called when a change is queried via {@link Compatibility#isChangeEnabled}
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ default boolean isChangeEnabled(long changeId) {
+ // Do not use String.format here (b/160912695)
+ // TODO(b/289900411): Rate limit this log if it's necessary in the release build.
+ // System.logW("No Compatibility callbacks set! Querying change " + changeId);
+ return true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public static final class ChangeConfig {
+ private final Set<Long> enabled;
+ private final Set<Long> disabled;
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) {
+ this.enabled = Objects.requireNonNull(enabled);
+ this.disabled = Objects.requireNonNull(disabled);
+ if (enabled.contains(null)) {
+ throw new NullPointerException();
+ }
+ if (disabled.contains(null)) {
+ throw new NullPointerException();
+ }
+ Set<Long> intersection = new HashSet<>(enabled);
+ intersection.retainAll(disabled);
+ if (!intersection.isEmpty()) {
+ throw new IllegalArgumentException("Cannot have changes " + intersection
+ + " enabled and disabled!");
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public boolean isEmpty() {
+ return enabled.isEmpty() && disabled.isEmpty();
+ }
+
+ private static long[] toLongArray(Set<Long> values) {
+ long[] result = new long[values.size()];
+ int idx = 0;
+ for (Long value: values) {
+ result[idx++] = value;
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public @NonNull long[] getEnabledChangesArray() {
+ return toLongArray(enabled);
+ }
+
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public @NonNull long[] getDisabledChangesArray() {
+ return toLongArray(disabled);
+ }
+
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public @NonNull Set<@NonNull Long> getEnabledSet() {
+ return Collections.unmodifiableSet(enabled);
+ }
+
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public @NonNull Set<@NonNull Long> getDisabledSet() {
+ return Collections.unmodifiableSet(disabled);
+ }
+
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public boolean isForceEnabled(long changeId) {
+ return enabled.contains(changeId);
+ }
+
+
+ /**
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @IntraCoreApi
+ public boolean isForceDisabled(long changeId) {
+ return disabled.contains(changeId);
+ }
+
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ChangeConfig)) {
+ return false;
+ }
+ ChangeConfig that = (ChangeConfig) o;
+ return enabled.equals(that.enabled) &&
+ disabled.equals(that.disabled);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled, disabled);
+ }
+
+
+ /**
+ * @hide
+ */
+ @Override
+ public String toString() {
+ return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}';
+ }
+ }
+
+ private static class OverrideCallbacks implements BehaviorChangeDelegate {
+ private final BehaviorChangeDelegate delegate;
+ private final ChangeConfig changeConfig;
+
+ private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) {
+ this.delegate = Objects.requireNonNull(delegate);
+ this.changeConfig = Objects.requireNonNull(changeConfig);
+ }
+ @Override
+ public boolean isChangeEnabled(long changeId) {
+ if (changeConfig.isForceEnabled(changeId)) {
+ return true;
+ }
+ if (changeConfig.isForceDisabled(changeId)) {
+ return false;
+ }
+ return delegate.isChangeEnabled(changeId);
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java
new file mode 100644
index 0000000..00730ef
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (C) 2018 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 libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is part of a contract provided by the "core" set of
+ * libraries to select parts of the Android software stack.
+ *
+ * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre>
+ * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with
+ * metalava's {@code --show-single-annotation} option and so must be applied at the class level and
+ * applied again each member that is to be made part of the API. Members that are not part of the
+ * API do not have to be explicitly hidden.
+ *
+ * @hide
+ */
+@IntraCoreApi
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface CorePlatformApi {
+
+ /** Enumeration of the possible statuses of the API in the core/platform API surface. */
+ @IntraCoreApi
+ enum Status {
+
+ /**
+ * This API is considered stable, and so present in both the stable and legacy version of
+ * the API surface.
+ */
+ @IntraCoreApi
+ STABLE,
+
+ /**
+ * This API is not (yet) considered stable, and so only present in the legacy version of
+ * the API surface.
+ */
+ @IntraCoreApi
+ LEGACY_ONLY
+ }
+
+ /** The status of the API in the core/platform API surface. */
+ @IntraCoreApi
+ Status status() default Status.LEGACY_ONLY;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java
new file mode 100644
index 0000000..f87ff11d
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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 libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that an API is hidden by default, in a similar fashion to the
+ * <pre>@hide</pre> javadoc tag.
+ *
+ * <p>Note that, in order for this to work, metalava has to be invoked with
+ * the flag {@code --hide-annotation libcore.api.Hide}.
+ *
+ * <p>This annotation should be used in {@code .annotated.java} stub files which
+ * contain API inclusion information about {@code libcore/ojluni} classes, to
+ * avoid patching the source files with <pre>@hide</pre> javadoc tags. All
+ * build targets which consume these stub files should also apply the above
+ * metalava flag.
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Hide {
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java
new file mode 100644
index 0000000..87cfcff2
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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 libcore.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is part of a contract within the "core" set of libraries, some of which may
+ * be mmodules.
+ *
+ * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre>
+ * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with
+ * metalava's {@code --show-single-annotation} option and so must be applied at the class level and
+ * applied again each member that is to be made part of the API. Members that are not part of the
+ * API do not have to be explicitly hidden.
+ *
+ * @hide
+ */
+@IntraCoreApi // @IntraCoreApi is itself part of the intra-core API
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface IntraCoreApi {
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
new file mode 100644
index 0000000..db3cd8ed
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 libcore.util;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a type use can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
+@libcore.api.IntraCoreApi
+public @interface NonNull {
+ /**
+ * Min Android API level (inclusive) to which this annotation is applied.
+ */
+ int from() default Integer.MIN_VALUE;
+
+ /**
+ * Max Android API level to which this annotation is applied.
+ */
+ int to() default Integer.MAX_VALUE;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
new file mode 100644
index 0000000..3371978
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 libcore.util;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a type use can be a null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
+@libcore.api.IntraCoreApi
+public @interface Nullable {
+ /**
+ * Min Android API level (inclusive) to which this annotation is applied.
+ */
+ int from() default Integer.MIN_VALUE;
+
+ /**
+ * Max Android API level to which this annotation is applied.
+ */
+ int to() default Integer.MAX_VALUE;
+}
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 3649f0e..b64944e 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -5,6 +5,10 @@
rename com/.*/nano/ devicenano/
rename android/.*/nano/ devicenano/
+
+# StatsD autogenerated classes. Maybe add a heuristic?
+class com.android.internal.util.FrameworkStatsLog keepclass
+
# Exported to Mainline modules; cannot use annotations
class com.android.internal.util.FastXmlSerializer keepclass
class com.android.internal.util.FileRotator keepclass
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a77ba62..ce1a292 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -64,6 +64,7 @@
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
@@ -396,7 +397,7 @@
mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
magnified.recycle();
}
@@ -474,8 +475,16 @@
return mIdOfLastServiceToMagnify;
}
+ /**
+ * This is invoked whenever magnification change happens.
+ *
+ * @param isScaleTransient represents that if the scale is being changed and the changed
+ * value may be short lived and be updated again soon.
+ * Calling the method usually notifies input manager to update the
+ * cursor scale, but setting this value {@code true} prevents it.
+ */
@GuardedBy("mLock")
- void onMagnificationChangedLocked() {
+ void onMagnificationChangedLocked(boolean isScaleTransient) {
final float scale = getScale();
final float centerX = getCenterX();
final float centerY = getCenterY();
@@ -498,6 +507,10 @@
} else {
hideThumbnail();
}
+
+ if (!isScaleTransient) {
+ notifyScaleForInput(mDisplayId, scale);
+ }
}
@GuardedBy("mLock")
@@ -611,8 +624,9 @@
* Directly Zooms out the scale to 1f with animating the transition. This method is
* triggered only by service automatically, such as when user context changed.
*/
+ @GuardedBy("mLock")
void zoomOutFromService() {
- setScaleAndCenter(1.0f, Float.NaN, Float.NaN,
+ setScaleAndCenter(1.0f, Float.NaN, Float.NaN, /* isScaleTransient= */ false,
transformToStubCallback(true),
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mZoomedOutFromService = true;
@@ -640,7 +654,7 @@
setActivated(false);
if (changed) {
spec.clear();
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
sendSpecToAnimation(spec, animationCallback);
@@ -651,7 +665,7 @@
}
@GuardedBy("mLock")
- boolean setScale(float scale, float pivotX, float pivotY,
+ boolean setScale(float scale, float pivotX, float pivotY, boolean isScaleTransient,
boolean animate, int id) {
if (!mRegistered) {
return false;
@@ -674,12 +688,14 @@
final float centerX = normPivotX + offsetX;
final float centerY = normPivotY + offsetY;
mIdOfLastServiceToMagnify = id;
- return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id);
+ return setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+ transformToStubCallback(animate), id);
}
@GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
- MagnificationAnimationCallback animationCallback, int id) {
+ boolean isScaleTransient, MagnificationAnimationCallback animationCallback,
+ int id) {
if (!mRegistered) {
return false;
}
@@ -696,7 +712,7 @@
+ animationCallback + ", id = " + id + ")");
}
boolean changed = setActivated(true);
- changed |= updateMagnificationSpecLocked(scale, centerX, centerY);
+ changed |= updateMagnificationSpecLocked(scale, centerX, centerY, isScaleTransient);
sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
if (isActivated() && (id != INVALID_SERVICE_ID)) {
mIdOfLastServiceToMagnify = id;
@@ -773,7 +789,9 @@
* @return {@code true} if the magnification spec changed or {@code false}
* otherwise
*/
- boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+ @GuardedBy("mLock")
+ boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY,
+ boolean isScaleTransient) {
// Handle defaults.
if (Float.isNaN(centerX)) {
centerX = getCenterX();
@@ -801,7 +819,7 @@
changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
if (changed) {
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(isScaleTransient);
}
return changed;
@@ -816,7 +834,7 @@
final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
if (id != INVALID_SERVICE_ID) {
mIdOfLastServiceToMagnify = id;
@@ -861,7 +879,7 @@
}
synchronized (mLock) {
mCurrentMagnificationSpec.setTo(lastSpecSent);
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
}
});
@@ -955,6 +973,7 @@
context,
traceManager,
LocalServices.getService(WindowManagerInternal.class),
+ LocalServices.getService(InputManagerInternal.class),
new Handler(context.getMainLooper()),
context.getResources().getInteger(R.integer.config_longAnimTime)),
lock,
@@ -1464,20 +1483,24 @@
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
- boolean animate, int id) {
+ boolean isScaleTransient, boolean animate, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
- return display.setScale(scale, pivotX, pivotY, animate, id);
+ return display.setScale(scale, pivotX, pivotY, isScaleTransient, animate, id);
}
}
@@ -1496,6 +1519,8 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1503,7 +1528,7 @@
return false;
}
return display.setScaleAndCenter(Float.NaN, centerX, centerY,
- animate ? STUB_ANIMATION_CALLBACK : null, id);
+ /* isScaleTransient= */ false, animate ? STUB_ANIMATION_CALLBACK : null, id);
}
}
@@ -1526,7 +1551,32 @@
*/
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate, int id) {
- return setScaleAndCenter(displayId, scale, centerX, centerY,
+ return setScaleAndCenter(displayId, scale, centerX, centerY, /* isScaleTransient= */ false,
+ transformToStubCallback(animate), id);
+ }
+
+ /**
+ * Sets the scale and center of the magnified region, optionally
+ * animating the transition. If animation is disabled, the transition
+ * is immediate.
+ *
+ * @param displayId The logical display id.
+ * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ * @param id the ID of the service requesting the change
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
+ */
+ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ boolean isScaleTransient, boolean animate, int id) {
+ return setScaleAndCenter(displayId, scale, centerX, centerY, isScaleTransient,
transformToStubCallback(animate), id);
}
@@ -1541,20 +1591,25 @@
* center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
* @param animationCallback Called when the animation result is valid.
* {@code null} to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
- MagnificationAnimationCallback animationCallback, int id) {
+ boolean isScaleTransient, MagnificationAnimationCallback animationCallback, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
- return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id);
+ return display.setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+ animationCallback, id);
}
}
@@ -1569,6 +1624,8 @@
* screen pixels.
* @param id the ID of the service requesting the change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1640,6 +1697,8 @@
*/
public void persistScale(int displayId) {
final float scale = getScale(displayId);
+ notifyScaleForInput(displayId, scale);
+
if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
@@ -1665,6 +1724,8 @@
*
* @param displayId The logical display id.
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
private void zoomOutFromService(int displayId) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1691,6 +1752,20 @@
}
/**
+ * Notifies input manager that magnification scale changed non-transiently
+ * so that pointer cursor is scaled as well.
+ *
+ * @param displayId The logical display id.
+ * @param scale The new scale factor.
+ */
+ public void notifyScaleForInput(int displayId, float scale) {
+ if (Flags.magnificationEnlargePointer()) {
+ mControllerCtx.getInputManager()
+ .setAccessibilityPointerIconScaleFactor(displayId, scale);
+ }
+ }
+
+ /**
* Resets all displays' magnification if last magnifying service is disabled.
*
* @param connectionId
@@ -2166,6 +2241,7 @@
private final Context mContext;
private final AccessibilityTraceManager mTrace;
private final WindowManagerInternal mWindowManager;
+ private final InputManagerInternal mInputManager;
private final Handler mHandler;
private final Long mAnimationDuration;
@@ -2175,11 +2251,13 @@
public ControllerContext(@NonNull Context context,
@NonNull AccessibilityTraceManager traceManager,
@NonNull WindowManagerInternal windowManager,
+ @NonNull InputManagerInternal inputManager,
@NonNull Handler handler,
long animationDuration) {
mContext = context;
mTrace = traceManager;
mWindowManager = windowManager;
+ mInputManager = inputManager;
mHandler = handler;
mAnimationDuration = animationDuration;
}
@@ -2209,6 +2287,14 @@
}
/**
+ * @return InputManagerInternal
+ */
+ @NonNull
+ public InputManagerInternal getInputManager() {
+ return mInputManager;
+ }
+
+ /**
* @return Handler for main looper
*/
@NonNull
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 963334b..c6a966f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -617,7 +617,8 @@
}
if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
- mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+ mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY,
+ /* isScaleTransient= */ true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
checkShouldDetectPassPersistedScale();
@@ -1974,6 +1975,7 @@
/* scale= */ scale,
/* centerX= */ mPivotEdge.x,
/* centerY= */ mPivotEdge.y,
+ /* isScaleTransient= */ true,
/* animate= */ true,
/* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
if (scale == 1.0f) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 1489d16..d40e747 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -176,7 +176,8 @@
public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
if (getFullScreenMagnificationController().isActivated(displayId)) {
getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
- Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
+ Float.NaN, Float.NaN, /* isScaleTransient= */ !updatePersistence, false,
+ MAGNIFICATION_GESTURE_HANDLER_ID);
if (updatePersistence) {
getFullScreenMagnificationController().persistScale(displayId);
}
@@ -371,7 +372,7 @@
}
screenMagnificationController.setScaleAndCenter(displayId, targetScale,
magnificationCenter.x, magnificationCenter.y,
- magnificationAnimationCallback, id);
+ /* isScaleTransient= */ false, magnificationAnimationCallback, id);
} else {
if (screenMagnificationController.isRegistered(displayId)) {
screenMagnificationController.reset(displayId, false);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 95281c8..5911070 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -715,11 +715,17 @@
@Override
public byte[] getBackupPayload(int userId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system");
+ }
return mBackupRestoreProcessor.getBackupPayload(userId);
}
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system");
+ }
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 39ac515..363807d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -68,6 +68,7 @@
import android.telephony.DisconnectCause;
import android.telephony.LinkCapacityEstimate;
import android.telephony.LocationAccessPolicy;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
import android.telephony.PhysicalChannelConfig;
@@ -90,6 +91,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.LocalLog;
import android.util.Pair;
import android.util.SparseArray;
@@ -429,6 +431,8 @@
private boolean[] mCarrierRoamingNtnMode = null;
private boolean[] mCarrierRoamingNtnEligible = null;
+ private List<IntArray> mCarrierRoamingNtnAvailableServices;
+
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
* type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -741,6 +745,7 @@
cutListToSize(mCarrierServiceStates, mNumPhones);
cutListToSize(mCallStateLists, mNumPhones);
cutListToSize(mMediaQualityStatus, mNumPhones);
+ cutListToSize(mCarrierRoamingNtnAvailableServices, mNumPhones);
return;
}
@@ -789,6 +794,7 @@
mSCBMDuration[i] = 0;
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
+ mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
}
}
}
@@ -864,6 +870,7 @@
mSCBMDuration = new long[numPhones];
mCarrierRoamingNtnMode = new boolean[numPhones];
mCarrierRoamingNtnEligible = new boolean[numPhones];
+ mCarrierRoamingNtnAvailableServices = new ArrayList<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -909,6 +916,7 @@
mSCBMDuration[i] = 0;
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
+ mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1533,6 +1541,15 @@
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnAvailableServicesChanged(
+ mCarrierRoamingNtnAvailableServices.get(r.phoneId).toArray());
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -3642,6 +3659,47 @@
}
}
+ /**
+ * Notify external listeners that carrier roaming non-terrestrial available services changed.
+ * @param availableServices The list of the supported services.
+ */
+ public void notifyCarrierRoamingNtnAvailableServicesChanged(
+ int subId, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) {
+ log("notifyCarrierRoamingNtnAvailableServicesChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnAvailableServicesChanged: "
+ + "availableServices=" + Arrays.toString(availableServices));
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
+ IntArray availableServicesIntArray = new IntArray(availableServices.length);
+ availableServicesIntArray.addAll(availableServices);
+ mCarrierRoamingNtnAvailableServices.set(phoneId, availableServicesIntArray);
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnAvailableServicesChanged(availableServices);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3706,6 +3764,8 @@
Pair<String, Integer> carrierServiceState = mCarrierServiceStates.get(i);
pw.println("mCarrierServiceState=<package=" + pii(carrierServiceState.first)
+ ", uid=" + carrierServiceState.second + ">");
+ pw.println("mCarrierRoamingNtnAvailableServices="
+ + mCarrierRoamingNtnAvailableServices.get(i));
pw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 947f6b7..51c768b 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -75,6 +75,7 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
import android.util.Slog;
@@ -82,7 +83,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BinderUtils;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.PermissionUtils;
import com.android.server.vcn.TelephonySubscriptionTracker;
@@ -448,7 +449,7 @@
final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
final UserManager userManager = mContext.getSystemService(UserManager.class);
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> {
if (!Objects.equals(userManager.getMainUser(), userHandle)) {
throw new SecurityException(
@@ -468,7 +469,7 @@
// TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> {
List<SubscriptionInfo> subsInGroup =
subMgr.getSubscriptionsInGroup(subscriptionGroup);
@@ -700,7 +701,7 @@
@GuardedBy("mLock")
private void notifyAllPolicyListenersLocked() {
for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
try {
policyListener.mListener.onPolicyChanged();
} catch (RemoteException e) {
@@ -715,7 +716,7 @@
@NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
if (isCallbackPermissioned(cbInfo, subGroup)) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
try {
cbInfo.mCallback.onVcnStatusChanged(statusCode);
} catch (RemoteException e) {
@@ -795,7 +796,7 @@
enforceManageTestNetworksForTestMode(config);
enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
synchronized (mLock) {
mConfigs.put(subscriptionGroup, config);
startOrUpdateVcnLocked(subscriptionGroup, config);
@@ -853,7 +854,7 @@
.checkPackage(mDeps.getBinderCallingUid(), opPkgName);
enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
synchronized (mLock) {
stopAndClearVcnConfigInternalLocked(subscriptionGroup);
writeConfigsToDiskLocked();
@@ -991,7 +992,7 @@
android.Manifest.permission.NETWORK_FACTORY,
android.Manifest.permission.MANAGE_TEST_NETWORKS);
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener);
synchronized (mLock) {
@@ -1018,7 +1019,7 @@
android.Manifest.permission.NETWORK_FACTORY,
android.Manifest.permission.MANAGE_TEST_NETWORKS);
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
synchronized (mLock) {
PolicyListenerBinderDeath listenerBinderDeath =
mRegisteredPolicyListeners.remove(listener.asBinder());
@@ -1082,7 +1083,7 @@
+ " MANAGE_TEST_NETWORKS");
}
- return Binder.withCleanCallingIdentity(() -> {
+ return BinderUtils.withCleanCallingIdentity(() -> {
// Defensive copy in case this call is in-process and the given NetworkCapabilities
// mutates
final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
@@ -1521,7 +1522,7 @@
// Notify all registered StatusCallbacks for this subGroup
for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
if (isCallbackPermissioned(cbInfo, mSubGroup)) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
try {
cbInfo.mCallback.onGatewayConnectionError(
gatewayConnectionName,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 217ef20..6ba8514 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,6 +60,7 @@
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
+import static android.content.Intent.isPreventIntentRedirectEnabled;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
import static android.content.pm.PackageManager.MATCH_ALL;
@@ -130,7 +131,6 @@
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
-import static android.security.Flags.preventIntentRedirect;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -19272,7 +19272,7 @@
* @hide
*/
public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
- if (!preventIntentRedirect()) return;
+ if (!isPreventIntentRedirectEnabled()) return;
if (intent == null || intent.getExtraIntentKeys() == null) return;
for (String key : intent.getExtraIntentKeys()) {
@@ -19283,7 +19283,9 @@
+ "} does not correspond to an intent in the extra bundle.");
continue;
}
- Slog.wtf(TAG, "A creator token is added to an intent.");
+ Slog.wtf(TAG,
+ "A creator token is added to an intent. creatorPackage: " + creatorPackage
+ + "; intent: " + intent);
IBinder creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
if (creatorToken != null) {
extraIntent.setCreatorToken(creatorToken);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c47cad9..28b606c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -3178,12 +3178,15 @@
mStats.collectPowerStatsSamples();
}
- BatteryUsageStats batteryUsageStats =
- mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query);
- if (proto) {
- batteryUsageStats.dumpToProto(fd);
- } else {
- batteryUsageStats.dump(pw, " ");
+ try (BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query)) {
+ if (proto) {
+ batteryUsageStats.dumpToProto(fd);
+ } else {
+ batteryUsageStats.dump(pw, " ");
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot close BatteryUsageStats", e);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index ba4b71c..17fcbf4 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -58,7 +58,7 @@
import com.android.modules.expresslog.Counter;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.wm.WindowProcessController;
import java.io.File;
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 1d68ee54..83b0801 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -67,6 +67,10 @@
mNormalBrightnessModeController.resetNbmData(
displayDeviceConfig.getLuxThrottlingData());
}
+ if (flags.useNewHdrBrightnessModifier()) {
+ // HDR boost is handled by HdrBrightnessModifier and should be disabled in HbmController
+ mHbmController.disableHdrBoost();
+ }
updateHdrClamper(info, displayToken, displayDeviceConfig);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 42a62f0..5b61f00 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1503,7 +1503,6 @@
// use the current brightness setting scaled by the doze scale factor
rawBrightnessState = getDozeBrightnessForOffload();
brightnessState = clampScreenBrightness(rawBrightnessState);
- updateScreenBrightnessSetting = false;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
mTempBrightnessEvent.setFlags(
mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
@@ -1513,6 +1512,7 @@
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
}
+ updateScreenBrightnessSetting = false;
}
if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 135cab6..6be0c12 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -38,6 +38,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.DisplayManagerService.Clock;
import com.android.server.display.config.HighBrightnessModeData;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
@@ -119,6 +120,14 @@
@Nullable
private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+ /**
+ * If {@link DisplayManagerFlags#useNewHdrBrightnessModifier()} is ON, hdr boost is handled by
+ * {@link com.android.server.display.brightness.clamper.HdrBrightnessModifier} and should be
+ * disabled in this class. After flag is cleaned up, this field together with HDR handling
+ * should be cleaned up from this class.
+ */
+ private boolean mHdrBoostDisabled = false;
+
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
@@ -323,6 +332,7 @@
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
pw.println(" width*height=" + mWidth + "*" + mHeight);
+ pw.println(" mHdrBoostDisabled=" + mHdrBoostDisabled);
if (mHighBrightnessModeMetadata != null) {
pw.println(" mRunningStartTimeMillis="
@@ -373,6 +383,11 @@
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
+ void disableHdrBoost() {
+ mHdrBoostDisabled = true;
+ unregisterHdrListener();
+ }
+
private long calculateRemainingTime(long currentTime) {
if (!deviceSupportsHbm()) {
return 0;
@@ -583,6 +598,9 @@
}
private void registerHdrListener(IBinder displayToken) {
+ if (mHdrBoostDisabled) {
+ return;
+ }
if (mRegisteredDisplayToken == displayToken) {
return;
}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 99f7f12..c888eef 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -262,4 +262,12 @@
*/
public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
int modifierState, @KeyGestureEvent.KeyGestureType int event);
+
+ /**
+ * Sets the magnification scale factor for pointer icons.
+ *
+ * @param displayId the ID of the display where the new scale factor is applied.
+ * @param scaleFactor the new scale factor to be applied for pointer icons.
+ */
+ public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8acf583..98e5319 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3506,6 +3506,11 @@
int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) {
mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType);
}
+
+ @Override
+ public void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+ InputManagerService.this.setAccessibilityPointerIconScaleFactor(displayId, scaleFactor);
+ }
}
@Override
@@ -3688,6 +3693,10 @@
mPointerIconCache.setPointerScale(scale);
}
+ void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+ mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
+ }
+
interface KeyboardBacklightControllerInterface {
default void incrementKeyboardBacklight(int deviceId) {}
default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index b488db5..2f5236f 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -23,6 +23,7 @@
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
import android.annotation.BinderThread;
import android.annotation.MainThread;
@@ -654,6 +655,18 @@
}
}
break;
+ case KeyEvent.KEYCODE_D:
+ if (enableMoveToNextDisplayShortcut()) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+ return handleKeyGesture(deviceId, new int[]{keyCode},
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ displayId,
+ focusedToken, /* flags = */0);
+ }
+ }
+ break;
case KeyEvent.KEYCODE_SLASH:
if (firstDown && event.isMetaPressed()) {
return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
index 297cd68..e16031c 100644
--- a/services/core/java/com/android/server/input/PointerIconCache.java
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
@@ -34,6 +35,7 @@
import android.view.PointerIcon;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.UiThread;
import java.util.Objects;
@@ -51,7 +53,7 @@
private final NativeInputManagerService mNative;
// We use the UI thread for loading pointer icons.
- private final Handler mUiThreadHandler = UiThread.getHandler();
+ private final Handler mUiThreadHandler;
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
@@ -70,6 +72,9 @@
POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
private float mPointerIconScale = DEFAULT_POINTER_SCALE;
+ // Note that android doesn't have SparseFloatArray, so this falls back to use double instead.
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private final SparseDoubleArray mAccessibilityScaleFactorPerDisplay = new SparseDoubleArray();
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@@ -86,6 +91,7 @@
mLoadedPointerIconsByDisplayAndType.remove(displayId);
mDisplayContexts.remove(displayId);
mDisplayDensities.delete(displayId);
+ mAccessibilityScaleFactorPerDisplay.delete(displayId);
}
}
@@ -96,8 +102,15 @@
};
/* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
+ this(context, nativeService, UiThread.getHandler());
+ }
+
+ @VisibleForTesting
+ /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService,
+ Handler handler) {
mContext = context;
mNative = nativeService;
+ mUiThreadHandler = handler;
}
public void systemRunning() {
@@ -134,6 +147,11 @@
mUiThreadHandler.post(() -> handleSetPointerScale(scale));
}
+ /** Set the scale for accessibility (magnification) for vector pointer icons. */
+ public void setAccessibilityScaleFactor(int displayId, float scaleFactor) {
+ mUiThreadHandler.post(() -> handleAccessibilityScaleFactor(displayId, scaleFactor));
+ }
+
/**
* Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
* it isn't already cached.
@@ -155,8 +173,10 @@
/* force= */ true);
theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
/* force= */ true);
+ final float scale = mPointerIconScale
+ * (float) mAccessibilityScaleFactorPerDisplay.get(displayId, 1f);
icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
- type, mUseLargePointerIcons, mPointerIconScale);
+ type, mUseLargePointerIcons, scale);
iconsByType.put(type, icon);
}
return Objects.requireNonNull(icon);
@@ -261,6 +281,19 @@
mNative.reloadPointerIcons();
}
+ @android.annotation.UiThread
+ private void handleAccessibilityScaleFactor(int displayId, float scale) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ if (mAccessibilityScaleFactorPerDisplay.get(displayId, 1f) == scale) {
+ return;
+ }
+ mAccessibilityScaleFactorPerDisplay.put(displayId, scale);
+ // Clear cached icons on the display.
+ mLoadedPointerIconsByDisplayAndType.remove(displayId);
+ }
+ mNative.reloadPointerIcons();
+ }
+
// Updates the cached display density for the given displayId, and returns true if
// the cached density changed.
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index d1576c5..509fa3e 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -127,42 +127,18 @@
@BinderThread
public void updateRuleSet(
String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver) {
- String ruleProvider = getCallerPackageNameOrThrow(Binder.getCallingUid());
- if (DEBUG_INTEGRITY_COMPONENT) {
- Slog.i(TAG, String.format("Calling rule provider name is: %s.", ruleProvider));
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_STATUS, STATUS_SUCCESS);
+ try {
+ statusReceiver.sendIntent(
+ mContext,
+ /* code= */ 0,
+ intent,
+ /* onFinished= */ null,
+ /* handler= */ null);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error sending status feedback.", e);
}
-
- mHandler.post(
- () -> {
- boolean success = true;
- try {
- mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
- } catch (Exception e) {
- Slog.e(TAG, "Error writing rules.", e);
- success = false;
- }
-
- if (DEBUG_INTEGRITY_COMPONENT) {
- Slog.i(
- TAG,
- String.format(
- "Successfully pushed rule set to version '%s' from '%s'",
- version, ruleProvider));
- }
-
- Intent intent = new Intent();
- intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
- try {
- statusReceiver.sendIntent(
- mContext,
- /* code= */ 0,
- intent,
- /* onFinished= */ null,
- /* handler= */ null);
- } catch (Exception e) {
- Slog.e(TAG, "Error sending status feedback.", e);
- }
- });
}
@Override
@@ -209,21 +185,6 @@
verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
}
- /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
- private String getPackageNameNormalized(String packageName) {
- if (packageName.length() <= 32) {
- return packageName;
- }
-
- try {
- MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
- byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
- return getHexDigest(hashBytes);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("SHA-256 algorithm not found", e);
- }
- }
-
private String getCallerPackageNameOrThrow(int callingUid) {
String callerPackageName = getCallingRulePusherPackageName(callingUid);
if (callerPackageName == null) {
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index ea4a6db..25741bc 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -81,6 +81,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -152,6 +153,8 @@
@interface MuteReason {}
private final Context mContext;
+ //This is NMS.mNotificationLock.
+ private final Object mLock;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
private final UserManager mUm;
@@ -165,6 +168,7 @@
private VibratorHelper mVibratorHelper;
// The last key in this list owns the hardware.
+ @GuardedBy("mLock")
ArrayList<String> mLights = new ArrayList<>();
private LogicalLight mNotificationLight;
private LogicalLight mAttentionLight;
@@ -183,8 +187,10 @@
private String mVibrateNotificationKey;
private boolean mSystemReady;
private boolean mInCallStateOffHook = false;
+ @GuardedBy("mLock")
private boolean mScreenOn = true;
private boolean mUserPresent = false;
+ @GuardedBy("mLock")
private boolean mNotificationPulseEnabled;
private final Uri mInCallNotificationUri;
private final AudioAttributes mInCallNotificationAudioAttributes;
@@ -200,12 +206,13 @@
private final PolitenessStrategy mStrategy;
private int mCurrentWorkProfileId = UserHandle.USER_NULL;
- public NotificationAttentionHelper(Context context, LightsManager lightsManager,
+ public NotificationAttentionHelper(Context context, Object lock, LightsManager lightsManager,
AccessibilityManager accessibilityManager, PackageManager packageManager,
UserManager userManager, NotificationUsageStats usageStats,
NotificationManagerPrivate notificationManagerPrivate,
ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
mContext = context;
+ mLock = lock;
mPackageManager = packageManager;
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mAccessibilityManager = accessibilityManager;
@@ -368,9 +375,11 @@
private void loadUserSettings() {
boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
- if (mNotificationPulseEnabled != pulseEnabled) {
- mNotificationPulseEnabled = pulseEnabled;
- updateLightsLocked();
+ synchronized (mLock) {
+ if (mNotificationPulseEnabled != pulseEnabled) {
+ mNotificationPulseEnabled = pulseEnabled;
+ updateLightsLocked();
+ }
}
if (Flags.politeNotifications()) {
@@ -1148,7 +1157,8 @@
}
}
- public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
+ public void dumpLocked(PrintWriter pw, String prefix,
+ NotificationManagerService.DumpFilter filter) {
pw.println("\n Notification attention state:");
pw.print(prefix);
pw.println(" mSoundNotificationKey=" + mSoundNotificationKey);
@@ -1684,16 +1694,22 @@
if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
- mScreenOn = true;
- updateLightsLocked();
+ synchronized (mLock) {
+ mScreenOn = true;
+ updateLightsLocked();
+ }
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
- mScreenOn = false;
- mUserPresent = false;
- updateLightsLocked();
+ synchronized (mLock) {
+ mScreenOn = false;
+ mUserPresent = false;
+ updateLightsLocked();
+ }
} else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK
.equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
- updateLightsLocked();
+ synchronized (mLock) {
+ updateLightsLocked();
+ }
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
mUserPresent = true;
// turn off LED when user passes through lock screen
@@ -1755,9 +1771,11 @@
Settings.System.NOTIFICATION_LIGHT_PULSE, 0,
UserHandle.USER_CURRENT)
!= 0;
- if (mNotificationPulseEnabled != pulseEnabled) {
- mNotificationPulseEnabled = pulseEnabled;
- updateLightsLocked();
+ synchronized (mLock) {
+ if (mNotificationPulseEnabled != pulseEnabled) {
+ mNotificationPulseEnabled = pulseEnabled;
+ updateLightsLocked();
+ }
}
}
if (Flags.politeNotifications()) {
@@ -1840,7 +1858,9 @@
@VisibleForTesting
void setScreenOn(boolean on) {
- mScreenOn = on;
+ synchronized (mLock) {
+ mScreenOn = on;
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e48c8ee..48cc032 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2640,9 +2640,9 @@
mToastRateLimiter = toastRateLimiter;
- mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
- mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
- mNotificationManagerPrivate, mZenModeHelper, flagResolver);
+ mAttentionHelper = new NotificationAttentionHelper(getContext(), mNotificationLock,
+ lightsManager, mAccessibilityManager, mPackageManagerClient, userManager,
+ usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver);
// register for various Intents.
// If this is called within a test, make sure to unregister the intent receivers by
@@ -7331,7 +7331,7 @@
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
- mAttentionHelper.dump(pw, " ", filter);
+ mAttentionHelper.dumpLocked(pw, " ", filter);
}
pw.println(" mArchive=" + mArchive.toString());
mArchive.dumpImpl(pw, filter);
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index a221152..d1d6ed0 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -34,16 +34,19 @@
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
+import java.util.Set;
public class BackgroundUserSoundNotifier {
@@ -65,6 +68,10 @@
*/
@VisibleForTesting
int mNotificationClientUid = -1;
+ /**
+ * UIDs of audio focus infos with active notifications.
+ */
+ Set<Integer> mNotificationClientUids = new ArraySet<>();
@VisibleForTesting
AudioPolicy mFocusControlAudioPolicy;
@VisibleForTesting
@@ -149,26 +156,42 @@
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
- if (mNotificationClientUid == -1) {
- return;
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ if (!intent.hasExtra(EXTRA_NOTIFICATION_CLIENT_UID)) {
+ return;
+ }
+ } else {
+ if (mNotificationClientUid == -1) {
+ return;
+ }
}
- dismissNotification(mNotificationClientUid);
+
+ int clientUid;
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ clientUid = intent.getIntExtra(EXTRA_NOTIFICATION_CLIENT_UID, -1);
+ } else {
+ clientUid = mNotificationClientUid;
+ }
+ dismissNotification(clientUid);
if (DEBUG) {
final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
final String action = intent.getAction().substring(actionIndex);
Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ ActivityManager.getCurrentUser() + " for alarm on user "
- + UserHandle.getUserHandleForUid(mNotificationClientUid));
+ + UserHandle.getUserHandleForUid(clientUid));
}
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
- muteAlarmSounds(mNotificationClientUid);
+ muteAlarmSounds(clientUid);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
+ activityManager.switchUser(UserHandle.getUserId(clientUid));
}
-
- mNotificationClientUid = -1;
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.remove(clientUid);
+ } else {
+ mNotificationClientUid = -1;
+ }
}
};
@@ -215,10 +238,11 @@
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
UserInfo userInfo = mUserManager.getUserInfo(userId);
+
// Only show notification if the sound is coming from background user and the notification
- // is not already shown.
+ // for this UID is not already shown.
if (userInfo != null && userId != foregroundContext.getUserId()
- && mNotificationClientUid == -1) {
+ && !isNotificationShown(afi.getClientUid())) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
if (DEBUG) {
@@ -226,8 +250,11 @@
+ ", displaying notification for current user "
+ foregroundContext.getUserId());
}
-
- mNotificationClientUid = afi.getClientUid();
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.add(afi.getClientUid());
+ } else {
+ mNotificationClientUid = afi.getClientUid();
+ }
mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
createNotification(userInfo.name, foregroundContext, afi.getClientUid()),
@@ -243,15 +270,21 @@
*/
@VisibleForTesting
void dismissNotificationIfNecessary(int notificationClientUid) {
+
if (getAudioFocusInfoForNotification(notificationClientUid) == null
- && mNotificationClientUid >= 0) {
+ && isNotificationShown(notificationClientUid)) {
if (DEBUG) {
Log.d(LOG_TAG, "Alarm ringing on background user "
+ UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier()
+ " left focus stack, dismissing notification");
}
dismissNotification(notificationClientUid);
- mNotificationClientUid = -1;
+
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.remove(notificationClientUid);
+ } else {
+ mNotificationClientUid = -1;
+ }
}
}
@@ -331,4 +364,12 @@
return notificationBuilder.build();
}
+
+ private boolean isNotificationShown(int notificationClientUid) {
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ return mNotificationClientUids.contains(notificationClientUid);
+ } else {
+ return mNotificationClientUid != -1;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index f69a017..35a69a2 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -22,6 +22,8 @@
import android.app.IAlarmManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
@@ -32,6 +34,9 @@
import android.util.SparseArray;
import android.view.Display;
+import com.android.server.LocalServices;
+import com.android.server.pm.pkg.AndroidPackage;
+
import java.io.PrintWriter;
import java.util.List;
@@ -266,11 +271,18 @@
ServiceManager.getService(Context.ALARM_SERVICE));
}
try {
- // This command is called by the shell, which has "com.android.shell" as package
- // name.
- pw.println("Schedule an alarm to wakeup in "
- + delayMillis + " ms, on behalf of shell.");
- mAlarmManager.set("com.android.shell",
+ PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage callingPackage =
+ packageManagerInternal.getPackage(Binder.getCallingUid());
+ if (callingPackage == null) {
+ pw.println("Calling uid " + Binder.getCallingUid() + " is not an android"
+ + " package. Cannot schedule a delayed wakeup on behalf of it.");
+ return -1;
+ }
+ pw.println("Schedule an alarm to wakeup in " + delayMillis +
+ " ms, on behalf of " + callingPackage.getPackageName());
+ mAlarmManager.set(callingPackage.getPackageName(),
AlarmManager.RTC_WAKEUP, wakeUpTime,
0, 0, AlarmManager.FLAG_PRIORITIZE,
null, mAlarmListener, "PowerManagerShellCommand", null, null);
diff --git a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
index dd6d5db..c0a06fc 100644
--- a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
@@ -51,6 +51,11 @@
mBatteryUsageStats.build().dump(ipw, "");
}
+ @Override
+ public void close() {
+ mBatteryUsageStats.discard();
+ }
+
static class Reader implements PowerStatsSpan.SectionReader {
@Override
public String getType() {
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 c04158f..48174a6 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11509,9 +11509,6 @@
mOnBatteryTimeBase);
}
- mPerDisplayBatteryStats = new DisplayBatteryStats[1];
- mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
-
mInteractiveTimer = new StopwatchTimer(mClock, null, -10, null, mOnBatteryTimeBase);
mPowerSaveModeEnabledTimer = new StopwatchTimer(mClock, null, -2, null,
mOnBatteryTimeBase);
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 265f1dfc..b996c43 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -295,7 +295,8 @@
stats.builder = ((AccumulatedBatteryUsageStatsSection) section)
.getBatteryUsageStatsBuilder();
stats.startWallClockTime = powerStatsSpan.getMetadata().getStartTime();
- stats.startMonotonicTime = powerStatsSpan.getMetadata().getStartMonotonicTime();
+ stats.startMonotonicTime =
+ powerStatsSpan.getMetadata().getStartMonotonicTime();
stats.endMonotonicTime = powerStatsSpan.getMetadata().getEndMonotonicTime();
break;
}
@@ -484,29 +485,30 @@
continue;
}
- PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
- spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
- if (powerStatsSpan == null) {
- continue;
- }
-
- for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
- BatteryUsageStats snapshot =
- ((BatteryUsageStatsSection) section).getBatteryUsageStats();
- if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
- customEnergyConsumerNames)) {
- Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
- + "custom power components: "
- + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ try (PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ spanMetadata.getId(), BatteryUsageStatsSection.TYPE)) {
+ if (powerStatsSpan == null) {
continue;
}
- if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
- Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
- + " does not include process state data");
- continue;
+ for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+ BatteryUsageStats snapshot =
+ ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+ if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ continue;
+ }
+
+ if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
+ + " does not include process state data");
+ continue;
+ }
+ builder.add(snapshot);
}
- builder.add(snapshot);
}
}
return builder.build();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
index af36524..eb896e9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -18,6 +18,7 @@
import android.os.BatteryUsageStats;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -28,6 +29,7 @@
class BatteryUsageStatsSection extends PowerStatsSpan.Section {
public static final String TYPE = "battery-usage-stats";
+ private static final String TAG = "BatteryUsageStatsSection";
private final BatteryUsageStats mBatteryUsageStats;
@@ -50,6 +52,15 @@
mBatteryUsageStats.dump(ipw, "");
}
+ @Override
+ public void close() {
+ try {
+ mBatteryUsageStats.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Closing BatteryUsageStats", e);
+ }
+ }
+
static class Reader implements PowerStatsSpan.SectionReader {
@Override
public String getType() {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index 5105272..4f560cf 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -51,7 +51,7 @@
/**
* Contains power stats of various kinds, aggregated over a time span.
*/
-public class PowerStatsSpan {
+public class PowerStatsSpan implements AutoCloseable {
private static final String TAG = "PowerStatsStore";
/**
@@ -321,6 +321,13 @@
public void dump(IndentingPrintWriter ipw) {
ipw.println(mType);
}
+
+ /**
+ * Closes the section, releasing any resources it held. Once closed, the Section
+ * should not be used.
+ */
+ public void close() {
+ }
}
/**
@@ -484,4 +491,10 @@
ipw.decreaseIndent();
}
}
+ @Override
+ public void close() {
+ for (int i = 0; i < mSections.size(); i++) {
+ mSections.get(i).close();
+ }
+ }
}
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 3673617..d83d355 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -331,9 +331,10 @@
ipw.increaseIndent();
List<PowerStatsSpan.Metadata> contents = getTableOfContents();
for (PowerStatsSpan.Metadata metadata : contents) {
- PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
- if (span != null) {
- span.dump(ipw);
+ try (PowerStatsSpan span = loadPowerStatsSpan(metadata.getId())) {
+ if (span != null) {
+ span.dump(ipw);
+ }
}
}
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
index 32dfdf9..5f93bdf 100644
--- a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
@@ -50,12 +50,14 @@
return;
}
- if (mScreenPowerStatsDescriptor == null) {
- mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor();
- if (mScreenPowerStatsDescriptor == null) {
- return;
- }
+ PowerStats.Descriptor screenDescriptor = screenStats.getPowerStatsDescriptor();
+ if (screenDescriptor == null) {
+ return;
+ }
+ if (mScreenPowerStatsDescriptor == null
+ || !mScreenPowerStatsDescriptor.equals(screenDescriptor)) {
+ mScreenPowerStatsDescriptor = screenDescriptor;
mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor);
mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength];
}
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index c8170a1..b2442c8 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -105,19 +105,19 @@
maxEndTime = spanMaxTime;
}
- PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
- AggregatedPowerStatsSection.TYPE);
- if (span == null) {
- Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
- continue;
- }
- List<PowerStatsSpan.Section> sections = span.getSections();
- for (int k = 0; k < sections.size(); k++) {
- hasStoredSpans = true;
- PowerStatsSpan.Section section = sections.get(k);
- populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
- ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
- // TODO(b/371614748): close the builder
+ try (PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ AggregatedPowerStatsSection.TYPE)) {
+ if (span == null) {
+ Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+ continue;
+ }
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ for (int k = 0; k < sections.size(); k++) {
+ hasStoredSpans = true;
+ PowerStatsSpan.Section section = sections.get(k);
+ populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+ ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+ }
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
index b295e30..8e7498f 100644
--- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
@@ -89,14 +89,15 @@
return true;
}
- mLastUsedDescriptor = descriptor;
- mStatsLayout = new ScreenPowerStatsLayout(descriptor);
- if (mStatsLayout.getDisplayCount() != mDisplayCount) {
- Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount()
+ ScreenPowerStatsLayout statsLayout = new ScreenPowerStatsLayout(descriptor);
+ if (statsLayout.getDisplayCount() != mDisplayCount) {
+ Slog.e(TAG, "Incompatible number of displays: " + statsLayout.getDisplayCount()
+ ", expected: " + mDisplayCount);
return false;
}
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = statsLayout;
mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
return true;
diff --git a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java
new file mode 100644
index 0000000..caca011
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java
@@ -0,0 +1,153 @@
+/*
+ * 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.security.forensic;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.forensic.ForensicEvent;
+import android.security.forensic.IBackupTransport;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BackupTransportConnection implements ServiceConnection {
+ private static final String TAG = "BackupTransportConnection";
+ private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
+ private final Context mContext;
+ private String mForensicBackupTransportConfig;
+ volatile IBackupTransport mService;
+
+ public BackupTransportConnection(Context context) {
+ mContext = context;
+ mService = null;
+ }
+
+ /**
+ * Initialize the BackupTransport binder service.
+ * @return Whether the initialization succeed.
+ */
+ public boolean initialize() {
+ if (!bindService()) {
+ return false;
+ }
+ AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+ try {
+ mService.initialize(resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ unbindService();
+ return false;
+ }
+ Integer result = getFutureResult(resultFuture);
+ if (result != null && result == 0) {
+ return true;
+ } else {
+ unbindService();
+ return false;
+ }
+ }
+
+ /**
+ * Add data to the BackupTransport binder service.
+ * @param data List of ForensicEvent.
+ * @return Whether the data is added to the binder service.
+ */
+ public boolean addData(List<ForensicEvent> data) {
+ AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+ try {
+ mService.addData(data, resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ return false;
+ }
+ Integer result = getFutureResult(resultFuture);
+ return result != null && result == 0;
+ }
+
+ /**
+ * Release the BackupTransport binder service.
+ */
+ public void release() {
+ AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+ try {
+ mService.release(resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ } finally {
+ unbindService();
+ }
+ }
+
+ private <T> T getFutureResult(AndroidFuture<T> future) {
+ try {
+ return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException
+ | CancellationException e) {
+ Slog.w(TAG, "Failed to get result from transport:", e);
+ return null;
+ }
+ }
+
+ private boolean bindService() {
+ mForensicBackupTransportConfig = mContext.getString(
+ com.android.internal.R.string.config_forensicBackupTransport);
+ if (TextUtils.isEmpty(mForensicBackupTransportConfig)) {
+ return false;
+ }
+
+ ComponentName serviceComponent =
+ ComponentName.unflattenFromString(mForensicBackupTransportConfig);
+ if (serviceComponent == null) {
+ return false;
+ }
+
+ Intent intent = new Intent().setComponent(serviceComponent);
+ boolean result = mContext.bindServiceAsUser(
+ intent, this, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ if (!result) {
+ unbindService();
+ }
+ return result;
+ }
+
+ private void unbindService() {
+ mContext.unbindService(this);
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IBackupTransport.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 07639d1..20c648e 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -63,6 +63,7 @@
private final Context mContext;
private final Handler mHandler;
+ private final BackupTransportConnection mBackupTransportConnection;
private final BinderService mBinderService;
private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -77,6 +78,7 @@
super(injector.getContext());
mContext = injector.getContext();
mHandler = new EventHandler(injector.getLooper(), this);
+ mBackupTransportConnection = injector.getBackupTransportConnection();
mBinderService = new BinderService(this);
}
@@ -221,6 +223,10 @@
private void enable(IForensicServiceCommandCallback callback) throws RemoteException {
switch (mState) {
case STATE_VISIBLE:
+ if (!mBackupTransportConnection.initialize()) {
+ callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
+ break;
+ }
mState = STATE_ENABLED;
notifyStateMonitors();
callback.onSuccess();
@@ -236,6 +242,7 @@
private void disable(IForensicServiceCommandCallback callback) throws RemoteException {
switch (mState) {
case STATE_ENABLED:
+ mBackupTransportConnection.release();
mState = STATE_VISIBLE;
notifyStateMonitors();
callback.onSuccess();
@@ -266,6 +273,8 @@
Context getContext();
Looper getLooper();
+
+ BackupTransportConnection getBackupTransportConnection();
}
private static final class InjectorImpl implements Injector {
@@ -289,6 +298,11 @@
serviceThread.start();
return serviceThread.getLooper();
}
+
+ @Override
+ public BackupTransportConnection getBackupTransportConnection() {
+ return new BackupTransportConnection(mContext);
+ }
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e7735d8..54e4f8e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -48,6 +48,9 @@
import static android.util.MathUtils.constrain;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
+import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines;
+import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
@@ -68,9 +71,6 @@
import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
@@ -209,6 +209,8 @@
import com.android.internal.os.LooperStats;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.ProcfsMemoryUtil;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
import com.android.internal.os.SelectedProcessCpuThreadReader;
import com.android.internal.os.StoragedUidIoStatsReader;
import com.android.internal.util.CollectionUtils;
@@ -229,7 +231,6 @@
import com.android.server.power.stats.KernelWakelockStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
import com.android.server.stats.pull.netstats.NetworkStatsExt;
import com.android.server.stats.pull.netstats.SubInfo;
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 1e82b89..baf84cf 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -40,11 +40,11 @@
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 5bc2c2d..1fba297 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -47,11 +47,11 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.util.LogUtils;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index a81ad22..189b608 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -90,11 +90,11 @@
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index 31ee247..78ff432 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -36,11 +36,11 @@
import android.os.HandlerExecutor;
import android.os.Looper;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import java.util.Objects;
import java.util.Set;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index ad5bc72..0b9b677 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -46,11 +46,11 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 53b0751..448a7eb 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -29,11 +29,11 @@
import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index 7ab8e55..1945052 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -22,10 +22,10 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.IndentingPrintWriter;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index c1f5a27..a2c2dfc 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -478,10 +478,10 @@
intentGrants.merge(creatorIntentGrants);
}
} catch (SecurityException securityException) {
- ActivityStarter.logForIntentRedirect(
+ ActivityStarter.logAndThrowExceptionForIntentRedirect(
"Creator URI Grant Caused Exception.", intent, creatorUid,
- creatorPackage, filterCallingUid, callingPackage);
- // TODO b/368559093 - rethrow the securityException.
+ creatorPackage, filterCallingUid, callingPackage,
+ securityException);
}
}
if ((aInfo.applicationInfo.privateFlags
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5d3ae54..2c4179f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -54,6 +54,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.content.pm.ActivityInfo.launchModeToString;
import static android.os.Process.INVALID_UID;
+import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -100,9 +101,11 @@
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
@@ -188,6 +191,10 @@
@Disabled
static final long ASM_RESTRICTIONS = 230590090L;
+ @ChangeId
+ @Overridable
+ private static final long ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION = 29623414L;
+
private final ActivityTaskManagerService mService;
private final RootWindowContainer mRootWindowContainer;
private final ActivityTaskSupervisor mSupervisor;
@@ -608,11 +615,10 @@
// Check if the Intent was redirected
if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
!= 0) {
- ActivityStarter.logForIntentRedirect(
+ ActivityStarter.logAndThrowExceptionForIntentRedirect(
"Unparceled intent does not have a creator token set.", intent,
- intentCreatorUid,
- intentCreatorPackage, resolvedCallingUid, resolvedCallingPackage);
- // TODO b/368559093 - eventually ramp up to throw SecurityException
+ intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage, null);
}
if (IntentCreatorToken.isValid(intent)) {
IntentCreatorToken creatorToken = (IntentCreatorToken) intent.getCreatorToken();
@@ -645,11 +651,10 @@
intentGrants.merge(creatorIntentGrants);
}
} catch (SecurityException securityException) {
- ActivityStarter.logForIntentRedirect(
+ ActivityStarter.logAndThrowExceptionForIntentRedirect(
"Creator URI Grant Caused Exception.", intent, intentCreatorUid,
intentCreatorPackage, resolvedCallingUid,
- resolvedCallingPackage);
- // TODO b/368559093 - rethrow the securityException.
+ resolvedCallingPackage, securityException);
}
}
} else {
@@ -670,11 +675,10 @@
intentGrants.merge(creatorIntentGrants);
}
} catch (SecurityException securityException) {
- ActivityStarter.logForIntentRedirect(
+ ActivityStarter.logAndThrowExceptionForIntentRedirect(
"Creator URI Grant Caused Exception.", intent, intentCreatorUid,
intentCreatorPackage, resolvedCallingUid,
- resolvedCallingPackage);
- // TODO b/368559093 - rethrow the securityException.
+ resolvedCallingPackage, securityException);
}
}
}
@@ -1045,7 +1049,7 @@
int callingUid = request.callingUid;
int intentCreatorUid = request.intentCreatorUid;
String intentCreatorPackage = request.intentCreatorPackage;
- String intentCallingPackage = request.callingPackage;
+ String callingPackage = request.callingPackage;
String callingFeatureId = request.callingFeatureId;
final int realCallingPid = request.realCallingPid;
final int realCallingUid = request.realCallingUid;
@@ -1130,7 +1134,7 @@
// launched in the app flow to redirect to an activity picked by the user, where
// we want the final activity to consider it to have been launched by the
// previous app activity.
- intentCallingPackage = sourceRecord.launchedFromPackage;
+ callingPackage = sourceRecord.launchedFromPackage;
callingFeatureId = sourceRecord.launchedFromFeatureId;
}
}
@@ -1152,7 +1156,7 @@
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
err = packageArchiver
.requestUnarchiveOnActivityStart(
- intent, intentCallingPackage, mRequest.userId, realCallingUid);
+ intent, callingPackage, mRequest.userId, realCallingUid);
}
}
}
@@ -1211,7 +1215,7 @@
boolean abort;
try {
abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
- requestCode, callingPid, callingUid, intentCallingPackage, callingFeatureId,
+ requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
resultRootTask);
} catch (SecurityException e) {
@@ -1239,7 +1243,7 @@
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
- intentCallingPackage);
+ callingPackage);
if (intentCreatorUid != Request.DEFAULT_INTENT_CREATOR_UID) {
try {
@@ -1247,36 +1251,29 @@
requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
request.ignoreTargetSecurity, inTask != null, null, resultRecord,
resultRootTask)) {
- logForIntentRedirect("Creator checkStartAnyActivityPermission Caused abortion.",
+ abort = logAndAbortForIntentRedirect(
+ "Creator checkStartAnyActivityPermission Caused abortion.",
intent, intentCreatorUid, intentCreatorPackage, callingUid,
- intentCallingPackage);
- // TODO b/368559093 - set abort to true.
- // abort = true;
+ callingPackage);
}
} catch (SecurityException e) {
- logForIntentRedirect("Creator checkStartAnyActivityPermission Caused Exception.",
- intent, intentCreatorUid, intentCreatorPackage, callingUid,
- intentCallingPackage);
- // TODO b/368559093 - rethrow the exception.
- //throw e;
+ logAndThrowExceptionForIntentRedirect(
+ "Creator checkStartAnyActivityPermission Caused Exception.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
+ e);
}
- if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid, 0,
- resolvedType, aInfo.applicationInfo)) {
- logForIntentRedirect("Creator IntentFirewall.checkStartActivity Caused abortion.",
- intent, intentCreatorUid, intentCreatorPackage, callingUid,
- intentCallingPackage);
- // TODO b/368559093 - set abort to true.
- // abort = true;
+ if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
+ 0, resolvedType, aInfo.applicationInfo)) {
+ abort = logAndAbortForIntentRedirect(
+ "Creator IntentFirewall.checkStartActivity Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
}
- if (!mService.getPermissionPolicyInternal().checkStartActivity(intent, intentCreatorUid,
- intentCreatorPackage)) {
- logForIntentRedirect(
+ if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
+ intentCreatorUid, intentCreatorPackage)) {
+ abort = logAndAbortForIntentRedirect(
"Creator PermissionPolicyService.checkStartActivity Caused abortion.",
- intent, intentCreatorUid, intentCreatorPackage, callingUid,
- intentCallingPackage);
- // TODO b/368559093 - set abort to true.
- // abort = true;
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
}
intent.removeCreatorTokenInfo();
}
@@ -1296,7 +1293,7 @@
balController.checkBackgroundActivityStart(
callingUid,
callingPid,
- intentCallingPackage,
+ callingPackage,
realCallingUid,
realCallingPid,
callerApp,
@@ -1317,7 +1314,7 @@
if (request.allowPendingRemoteAnimationRegistryLookup) {
checkedOptions = mService.getActivityStartController()
.getPendingRemoteAnimationRegistry()
- .overrideOptionsIfNeeded(intentCallingPackage, checkedOptions);
+ .overrideOptionsIfNeeded(callingPackage, checkedOptions);
}
if (mService.mController != null) {
try {
@@ -1334,7 +1331,7 @@
final TaskDisplayArea suggestedLaunchDisplayArea =
computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags,
- intentCallingPackage,
+ callingPackage,
callingFeatureId);
if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
@@ -1372,7 +1369,7 @@
if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
aInfo.packageName, userId)) {
final IIntentSender target = mService.getIntentSenderLocked(
- ActivityManager.INTENT_SENDER_ACTIVITY, intentCallingPackage,
+ ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
callingFeatureId,
callingUid, userId, null, null, 0, new Intent[]{intent},
new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
@@ -1436,7 +1433,7 @@
// app [on install success].
if (rInfo != null && rInfo.auxiliaryInfo != null) {
intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent,
- intentCallingPackage, callingFeatureId, verificationBundle, resolvedType,
+ callingPackage, callingFeatureId, verificationBundle, resolvedType,
userId);
resolvedType = null;
callingUid = realCallingUid;
@@ -1460,7 +1457,7 @@
.setCaller(callerApp)
.setLaunchedFromPid(callingPid)
.setLaunchedFromUid(callingUid)
- .setLaunchedFromPackage(intentCallingPackage)
+ .setLaunchedFromPackage(callingPackage)
.setLaunchedFromFeature(callingFeatureId)
.setIntent(intent)
.setResolvedType(resolvedType)
@@ -3588,16 +3585,32 @@
pw.println(mInTaskFragment);
}
- static void logForIntentRedirect(String message, Intent intent, int intentCreatorUid,
- String intentCreatorPackage, int callingUid, String callingPackage) {
+ static void logAndThrowExceptionForIntentRedirect(@NonNull String message,
+ @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+ int callingUid, @Nullable String callingPackage,
+ @Nullable SecurityException originalException) {
String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
intentCreatorPackage, callingUid, callingPackage);
Slog.wtf(TAG, msg);
+ if (preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+ ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid)) {
+ throw new SecurityException(msg, originalException);
+ }
}
- private static String getIntentRedirectPreventedLogMessage(String message, Intent intent,
- int intentCreatorUid, String intentCreatorPackage, int callingUid,
- String callingPackage) {
+ private static boolean logAndAbortForIntentRedirect(@NonNull String message,
+ @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+ int callingUid, @Nullable String callingPackage) {
+ String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ intentCreatorPackage, callingUid, callingPackage);
+ Slog.wtf(TAG, msg);
+ return preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+ ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
+ }
+
+ private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
+ @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+ int callingUid, @Nullable String callingPackage) {
return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+ "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ "; callingPackage: " + callingPackage + "; intent: " + intent;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4f71719..567eca2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2572,14 +2572,21 @@
wpc.computeProcessActivityState();
}
- void computeProcessActivityStateBatch() {
+ boolean computeProcessActivityStateBatch() {
if (mActivityStateChangedProcs.isEmpty()) {
- return;
+ return false;
}
+ boolean changed = false;
for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
- mActivityStateChangedProcs.get(i).computeProcessActivityState();
+ final WindowProcessController wpc = mActivityStateChangedProcs.get(i);
+ final int prevState = wpc.getActivityStateFlags();
+ wpc.computeProcessActivityState();
+ if (!changed && prevState != wpc.getActivityStateFlags()) {
+ changed = true;
+ }
}
mActivityStateChangedProcs.clear();
+ return changed;
}
/**
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index ccd5996..6cc4b1e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -994,7 +994,9 @@
// Ensure the final animation targets which hidden by transition could be visible.
for (int i = 0; i < targets.size(); i++) {
final WindowContainer wc = targets.get(i).mContainer;
- wc.prepareSurfaces();
+ if (wc.mSurfaceControl != null) {
+ wc.prepareSurfaces();
+ }
}
// The pending builder could be cleared due to prepareSurfaces
@@ -1328,16 +1330,13 @@
if (!allWindowDrawn) {
return;
}
- final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
- if (startingSurface != null && startingSurface.isValid()) {
- startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
- synchronized (mWindowManagerService.mGlobalLock) {
- if (mOpenAnimAdaptor != null) {
- mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
- }
+ startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+ synchronized (mWindowManagerService.mGlobalLock) {
+ if (mOpenAnimAdaptor != null) {
+ mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
}
- });
- }
+ }
+ });
}
void clearBackAnimateTarget(boolean cancel) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 41a5804..6707a27 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -105,6 +105,7 @@
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.power.Mode;
@@ -153,6 +154,7 @@
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.Slogf;
+import com.android.server.wm.utils.RegionUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -270,6 +272,8 @@
private boolean mTaskLayersChanged = true;
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
+ private Region mTmpOccludingRegion;
+ private Region mTmpTaskRegion;
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@@ -2921,6 +2925,11 @@
});
}
+ void invalidateTaskLayersAndUpdateOomAdjIfNeeded() {
+ mRankTaskLayersRunnable.mCheckUpdateOomAdj = true;
+ invalidateTaskLayers();
+ }
+
void invalidateTaskLayers() {
if (!mTaskLayersChanged) {
mTaskLayersChanged = true;
@@ -2938,13 +2947,18 @@
// Only rank for leaf tasks because the score of activity is based on immediate parent.
forAllLeafTasks(task -> {
final int oldRank = task.mLayerRank;
- final ActivityRecord r = task.topRunningActivityLocked();
- if (r != null && r.isVisibleRequested()) {
+ final int oldRatio = task.mNonOccludedFreeformAreaRatio;
+ task.mNonOccludedFreeformAreaRatio = 0;
+ if (task.isVisibleRequested()) {
task.mLayerRank = ++mTmpTaskLayerRank;
+ if (task.inFreeformWindowingMode()) {
+ computeNonOccludedFreeformAreaRatio(task);
+ }
} else {
task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
}
- if (task.mLayerRank != oldRank) {
+ if (task.mLayerRank != oldRank
+ || task.mNonOccludedFreeformAreaRatio != oldRatio) {
task.forAllActivities(activity -> {
if (activity.hasProcess()) {
mTaskSupervisor.onProcessActivityStateChanged(activity.app,
@@ -2953,10 +2967,40 @@
});
}
}, true /* traverseTopToBottom */);
-
- if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
- mTaskSupervisor.computeProcessActivityStateBatch();
+ if (mTmpOccludingRegion != null) {
+ mTmpOccludingRegion.setEmpty();
}
+ boolean changed = false;
+ if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+ changed = mTaskSupervisor.computeProcessActivityStateBatch();
+ }
+ if (mRankTaskLayersRunnable.mCheckUpdateOomAdj) {
+ mRankTaskLayersRunnable.mCheckUpdateOomAdj = false;
+ if (changed) {
+ mService.updateOomAdj();
+ }
+ }
+ }
+
+ /** This method is called for visible freeform task from top to bottom. */
+ private void computeNonOccludedFreeformAreaRatio(@NonNull Task task) {
+ if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+ return;
+ }
+ if (mTmpOccludingRegion == null) {
+ mTmpOccludingRegion = new Region();
+ mTmpTaskRegion = new Region();
+ }
+ final Rect taskBounds = task.getBounds();
+ mTmpTaskRegion.set(taskBounds);
+ // Exclude the area outside the display.
+ mTmpTaskRegion.op(task.mDisplayContent.getBounds(), Region.Op.INTERSECT);
+ // Exclude the area covered by the above tasks.
+ mTmpTaskRegion.op(mTmpOccludingRegion, Region.Op.DIFFERENCE);
+ task.mNonOccludedFreeformAreaRatio = 100 * RegionUtils.getAreaSize(mTmpTaskRegion)
+ / (taskBounds.width() * taskBounds.height());
+ // Accumulate the occluding region for other visible tasks behind.
+ mTmpOccludingRegion.op(taskBounds, Region.Op.UNION);
}
void clearOtherAppTimeTrackers(AppTimeTracker except) {
@@ -3862,6 +3906,8 @@
}
private class RankTaskLayersRunnable implements Runnable {
+ boolean mCheckUpdateOomAdj;
+
@Override
public void run() {
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4861341..8a624b3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -432,6 +432,9 @@
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = LAYER_RANK_INVISIBLE;
+ /** A 0~100 ratio to indicate the percentage of visible area on screen of a freeform task. */
+ int mNonOccludedFreeformAreaRatio;
+
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -1207,6 +1210,28 @@
}
@Override
+ void onResize() {
+ super.onResize();
+ updateTaskLayerForFreeform();
+ }
+
+ @Override
+ void onMovedByResize() {
+ super.onMovedByResize();
+ updateTaskLayerForFreeform();
+ }
+
+ private void updateTaskLayerForFreeform() {
+ if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+ return;
+ }
+ if (!isVisibleRequested() || !inFreeformWindowingMode()) {
+ return;
+ }
+ mRootWindowContainer.invalidateTaskLayersAndUpdateOomAdjIfNeeded();
+ }
+
+ @Override
@Nullable
ActivityRecord getTopResumedActivity() {
if (!isLeafTask()) {
@@ -3410,6 +3435,36 @@
info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
+ info.topActivityMainWindowFrame = calculateTopActivityMainWindowFrameForTaskInfo(top);
+ }
+
+ /**
+ * Returns the top activity's main window frame if it doesn't match
+ * {@link ActivityRecord#getBounds() the top activity bounds}, or {@code null}, otherwise.
+ *
+ * @param top The top running activity of the task
+ */
+ @Nullable
+ private static Rect calculateTopActivityMainWindowFrameForTaskInfo(
+ @Nullable ActivityRecord top) {
+ if (!Flags.betterSupportNonMatchParentActivity()) {
+ return null;
+ }
+ if (top == null) {
+ return null;
+ }
+ final WindowState mainWindow = top.findMainWindow();
+ if (mainWindow == null) {
+ return null;
+ }
+ if (!mainWindow.mHaveFrame) {
+ return null;
+ }
+ final Rect windowFrame = mainWindow.getFrame();
+ if (top.getBounds().equals(windowFrame)) {
+ return null;
+ }
+ return windowFrame;
}
/**
@@ -5919,6 +5974,10 @@
pw.print(prefix); pw.print(" mLastNonFullscreenBounds=");
pw.println(mLastNonFullscreenBounds);
}
+ if (mNonOccludedFreeformAreaRatio != 0) {
+ pw.print(prefix); pw.print(" mNonOccludedFreeformAreaRatio=");
+ pw.println(mNonOccludedFreeformAreaRatio);
+ }
if (isLeafTask()) {
pw.println(prefix + " isSleeping=" + shouldSleepActivities());
printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3ffeacf..d6ba312 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -3027,7 +3027,11 @@
// The task order may be changed by finishIfPossible() for adjusting focus if there are
// nested tasks, so add all activities into a list to avoid missed removals.
final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
- forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
+ forAllActivities((r) -> {
+ if (!r.finishing) {
+ removingActivities.add(r);
+ }
+ });
for (int i = removingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = removingActivities.get(i);
if (withTransition && r.isVisible()) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1a107c2..7f0c336 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -121,6 +121,11 @@
*/
private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+ /**
+ * If the visible area percentage of a resumed freeform task is greater than or equal to this
+ * ratio, its process will have a higher priority.
+ */
+ private static final int PERCEPTIBLE_FREEFORM_VISIBLE_RATIO = 90;
private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
@@ -1234,6 +1239,7 @@
boolean hasResumedFreeform = false;
int minTaskLayer = Integer.MAX_VALUE;
int stateFlags = 0;
+ int nonOccludedRatio = 0;
final boolean wasResumed = hasResumedActivity();
final boolean wasAnyVisible = (mActivityStateFlags
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1261,6 +1267,8 @@
stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
} else if (windowingMode == WINDOWING_MODE_FREEFORM) {
hasResumedFreeform = true;
+ nonOccludedRatio =
+ Math.max(task.mNonOccludedFreeformAreaRatio, nonOccludedRatio);
}
}
if (minTaskLayer > 0) {
@@ -1298,7 +1306,8 @@
&& com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
// Exclude task layer 1 because it is already the top most.
&& minTaskLayer > 1) {
- if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM
+ || nonOccludedRatio >= PERCEPTIBLE_FREEFORM_VISIBLE_RATIO) {
stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
} else {
stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ce7776f..ff23145 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -81,4 +81,15 @@
Collections.reverse(rects);
rects.forEach(rectConsumer);
}
+
+ /** Returns the area size of the region. */
+ public static int getAreaSize(Region region) {
+ final RegionIterator regionIterator = new RegionIterator(region);
+ int area = 0;
+ final Rect rect = new Rect();
+ while (regionIterator.next(rect)) {
+ area += rect.width() * rect.height();
+ }
+ return area;
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0b7ce75..2461c1c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,6 +106,7 @@
import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
+import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogConfigurationServiceImpl;
@@ -1495,8 +1496,7 @@
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
- boolean isWatch = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
+ boolean isWatch = RoSystemFeatures.hasFeatureWatch(context);
boolean isArc = context.getPackageManager().hasSystemFeature(
"org.chromium.arc");
@@ -1504,6 +1504,8 @@
boolean isTv = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
+ boolean isAutomotive = RoSystemFeatures.hasFeatureAutomotive(context);
+
boolean enableVrService = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
@@ -1760,7 +1762,8 @@
t.traceEnd();
}
- if (android.security.Flags.aapmApi()) {
+ if (!isWatch && !isTv && !isAutomotive
+ && android.security.Flags.aapmApi()) {
t.traceBegin("StartAdvancedProtectionService");
mSystemServiceManager.startService(AdvancedProtectionService.Lifecycle.class);
t.traceEnd();
@@ -2758,7 +2761,7 @@
t.traceEnd();
}
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+ if (RoSystemFeatures.hasFeatureEmbedded(context)) {
t.traceBegin("StartIoTSystemService");
mSystemServiceManager.startService(IOT_SERVICE_CLASS);
t.traceEnd();
@@ -3137,8 +3140,6 @@
}, WEBVIEW_PREPARATION);
}
- boolean isAutomotive = mPackageManager
- .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
if (isAutomotive) {
t.traceBegin("StartCarServiceHelperService");
final SystemService cshs = mSystemServiceManager
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index f15e533..2f00a1b 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -32,6 +32,7 @@
"androidx.test.runner",
"truth",
"Harrier",
+ "bedstead-multiuser",
],
platform_apis: true,
certificate: "platform",
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index 70a2d48..48cebd7 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.MOVE_PACKAGE;
import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
@@ -112,9 +113,9 @@
final UserReference primaryUser = sDeviceState.primaryUser();
if (primaryUser.id() == UserHandle.myUserId()) {
mCurrentUser = primaryUser;
- mOtherUser = sDeviceState.secondaryUser();
+ mOtherUser = secondaryUser(sDeviceState);
} else {
- mCurrentUser = sDeviceState.secondaryUser();
+ mCurrentUser = secondaryUser(sDeviceState);
mOtherUser = primaryUser;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index fdf6b80..9189c2f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2224,6 +2224,8 @@
verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
eq(false));
+ // This brightness shouldn't be stored in the setting
+ verify(mHolder.brightnessSetting, never()).setBrightness(DEFAULT_DOZE_BRIGHTNESS);
// The display device changes and the default doze brightness changes
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 5c718d9..b2fe138 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1726,6 +1726,8 @@
}
// Top-started job
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-stared jobs are out of quota enforcement.
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
trackJobs(job, jobDefIWF, jobHigh);
@@ -1755,6 +1757,38 @@
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
}
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Quota is enforced for top-started job after the process leaves TOP/BTOP state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(job, jobDefIWF, jobHigh);
+ mQuotaController.prepareForExecutionLocked(job);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobHigh);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
+ }
}
@Test
@@ -1824,6 +1858,7 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1831,6 +1866,7 @@
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1842,6 +1878,28 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket
+ // minus the time has been used.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
// Test used quota rolling out of window.
synchronized (mQuotaController.mLock) {
mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
@@ -1856,6 +1914,7 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1864,6 +1923,7 @@
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1874,6 +1934,28 @@
assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
}
@Test
@@ -1902,6 +1984,7 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1909,6 +1992,7 @@
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1920,6 +2004,27 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
// Test used quota rolling out of window.
synchronized (mQuotaController.mLock) {
mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
@@ -1935,6 +2040,7 @@
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1943,6 +2049,7 @@
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1953,6 +2060,28 @@
assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
}
/**
@@ -4608,6 +4737,7 @@
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Bg job starts while inactive, spans an entire active session, and ends after the
// active session.
@@ -4686,8 +4816,66 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
}
+ // jobBg2 and jobFg1 are counted, jobTop is not counted.
expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+ // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+ // foreground_service and a new job starts. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 2, which accounts for the bg2 and fg and top jobs.
+ // Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+ }
+ // jobBg2, jobFg1 and jobTop are counted.
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
@@ -4807,7 +4995,8 @@
* Tests that TOP jobs aren't stopped when an app runs out of quota.
*/
@Test
- public void testTracking_OutOfQuota_ForegroundAndBackground() {
+ @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
setDischarging();
JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
@@ -4851,6 +5040,7 @@
// Go to a background state.
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Only Bg job will be changed from in-quota to out-of-quota.
inOrder.verify(mJobSchedulerService,
timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
.onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
@@ -4897,6 +5087,105 @@
}
/**
+ * Tests that TOP jobs are stopped when an app runs out of quota.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+ setDischarging();
+
+ JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+ trackJobs(jobBg, jobTop);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Both Bg and Top jobs should be changed from in-quota to out-of-quota
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ // Top job should NOT be allowed to run.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // New jobs to run.
+ JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+ JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ // Both Bg and Top jobs should be changed from out-of-quota to in-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ trackJobs(jobFg, jobTop);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ // App is in background so everything should be out of quota.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Bg, Fg and Top jobs should be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ // App is now in background and out of quota. Fg should now change to out of quota
+ // since it wasn't started. Top should now changed to out of quota even it started
+ // when the app was in TOP.
+ assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ }
+
+ /**
* Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
* its quota.
*/
@@ -6280,6 +6569,7 @@
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
// foreground_service and a new job starts. Shortly after, uid goes
@@ -6333,6 +6623,63 @@
expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+ // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+ // foreground_service and a new job starts. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 3, which accounts for the bg2, fg and top jobs.
+ // Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+ }
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
@@ -6701,7 +7048,8 @@
* Tests that expedited jobs aren't stopped when an app runs out of quota.
*/
@Test
- public void testEJTracking_OutOfQuota_ForegroundAndBackground() {
+ @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testEJTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
setDischarging();
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
@@ -6813,6 +7161,129 @@
}
/**
+ * Tests that expedited jobs are stopped when an app runs out of quota.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testEJTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+ setDischarging();
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
+
+ JobStatus jobBg =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 2);
+ JobStatus jobUnstarted =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 3);
+ trackJobs(jobBg, jobTop, jobUnstarted);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg, jobUnstarted);
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ mQcConstants.EJ_LIMIT_WORKING_MS - remainingTimeMs, 1), true);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Bg, Top and jobUnstarted should be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ // Top should still NOT be "in quota" even it started before the app
+ // ran on top out of quota.
+ assertFalse(jobBg.isExpeditedQuotaApproved());
+ assertFalse(jobTop.isExpeditedQuotaApproved());
+ assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+ synchronized (mQuotaController.mLock) {
+ assertTrue(
+ 0 >= mQuotaController
+ .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ // New jobs to run.
+ JobStatus jobBg2 =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobTop2 =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 5);
+ JobStatus jobFg =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 6);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
+ // jobBg, jobFg and jobUnstarted are changed from out-of-quota to in-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ trackJobs(jobTop2, jobFg);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop2);
+ }
+ assertTrue(jobTop.isExpeditedQuotaApproved());
+ assertTrue(jobTop2.isExpeditedQuotaApproved());
+ assertTrue(jobFg.isExpeditedQuotaApproved());
+ assertTrue(jobBg.isExpeditedQuotaApproved());
+ assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ assertTrue(jobTop.isExpeditedQuotaApproved());
+ assertTrue(jobTop2.isExpeditedQuotaApproved());
+ assertTrue(jobFg.isExpeditedQuotaApproved());
+ assertTrue(jobBg.isExpeditedQuotaApproved());
+ assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Bg, Fg, Top, Top2 and jobUnstarted should be changed from in-quota to out-of-quota
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 5));
+ // App is now in background and out of quota. Fg should now change to out of quota since it
+ // wasn't started. Top should change to out of quota as the app leaves TOP state.
+ assertFalse(jobTop.isExpeditedQuotaApproved());
+ assertFalse(jobTop2.isExpeditedQuotaApproved());
+ assertFalse(jobFg.isExpeditedQuotaApproved());
+ assertFalse(jobBg.isExpeditedQuotaApproved());
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isExpeditedQuotaApproved());
+ assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+ synchronized (mQuotaController.mLock) {
+ assertTrue(
+ 0 >= mQuotaController
+ .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
+ /**
* Tests that Timers properly track overlapping top and background jobs.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index 625dbe6..bf946a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -41,14 +41,19 @@
import android.media.AudioPlaybackConfiguration;
import android.media.PlayerProxy;
import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -60,7 +65,6 @@
import java.util.Stack;
@RunWith(JUnit4.class)
-
public class BackgroundUserSoundNotifierTest {
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -72,6 +76,10 @@
@Mock
private NotificationManager mNotificationManager;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -142,8 +150,11 @@
final int fgUserId = mSpiedContext.getUserId();
int bgUserId = fgUserId + 1;
int bgUserUid = bgUserId * 100000;
- mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
-
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mBackgroundUserSoundNotifier.mNotificationClientUids.add(bgUserUid);
+ } else {
+ mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+ }
AudioManager mockAudioManager = mock(AudioManager.class);
when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
@@ -243,6 +254,72 @@
notification.actions[0].title);
}
+ @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+ @Test
+ public void testMultipleAlarmsSameUid_OneNotificationCreated() throws RemoteException {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ final int fgUserId = mSpiedContext.getUserId();
+ final int bgUserUid = user.id * 100000;
+ doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi1.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+
+ AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+ clearInvocations(mNotificationManager);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+ verify(mNotificationManager, never())
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi2.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+ @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+ @Test
+ public void testMultipleAlarmsDifferentUsers_multipleNotificationsCreated()
+ throws RemoteException {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ UserInfo user1 = createUser("User1", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ UserInfo user2 = createUser("User2", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ final int fgUserId = mSpiedContext.getUserId();
+ final int bgUserUid1 = user1.id * 100000;
+ final int bgUserUid2 = user2.id * 100000;
+ doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid1, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi1.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+
+ AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid2, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+ clearInvocations(mNotificationManager);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi2.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
if (user != null) {
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 177f30a..0a1fc31 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
@@ -987,5 +987,7 @@
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isEqualTo(60000);
+
+ span.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
index 9a64ce1..94997b2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -41,7 +41,7 @@
public final RavenwoodRule mRavenwood = new RavenwoodRule();
@Test
- public void testBatteryUsageStatsDataConsistency() {
+ public void testBatteryUsageStatsDataConsistency() throws Exception {
BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
BatteryUsageStats stats = bsm.getBatteryUsageStats(
new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
@@ -70,5 +70,7 @@
}
}
}
+
+ stats.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 7d2bc17..813dd84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -64,7 +64,7 @@
private static final int UID_3 = 4000;
@Test
- public void testAtom_BatteryUsageStatsPerUid() {
+ public void testAtom_BatteryUsageStatsPerUid() throws Exception {
final BatteryUsageStats bus = buildBatteryUsageStats();
BatteryStatsService.FrameworkStatsLogger statsLogger =
mock(BatteryStatsService.FrameworkStatsLogger.class);
@@ -72,6 +72,8 @@
List<StatsEvent> actual = new ArrayList<>();
new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual);
+ bus.close();
+
if (DEBUG) {
System.out.println(mockingDetails(statsLogger).printInvocations());
}
@@ -284,7 +286,7 @@
}
@Test
- public void testAtom_BatteryUsageStatsAtomsProto() {
+ public void testAtom_BatteryUsageStatsAtomsProto() throws Exception {
final BatteryUsageStats bus = buildBatteryUsageStats();
final byte[] bytes = bus.getStatsProto();
BatteryUsageStatsAtomsProto proto;
@@ -347,6 +349,7 @@
// UID_3 - Should be none, since no interesting data (done last for debugging convenience).
assertEquals(3, proto.uidBatteryConsumers.length);
+ bus.close();
}
private void assertSameBatteryConsumer(String message, BatteryConsumer consumer,
@@ -582,7 +585,7 @@
}
@Test
- public void testLargeAtomTruncated() {
+ public void testLargeAtomTruncated() throws Exception {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
@@ -618,6 +621,8 @@
assertThat(bytes.length).isGreaterThan(20000);
assertThat(bytes.length).isLessThan(50000);
+ batteryUsageStats.close();
+
BatteryUsageStatsAtomsProto proto;
try {
proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
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 e9d95fc..87a26d0 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
@@ -94,7 +94,7 @@
}
@Test
- public void test_getBatteryUsageStats() {
+ public void test_getBatteryUsageStats() throws IOException {
final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
final List<UidBatteryConsumer> uidBatteryConsumers =
@@ -121,24 +121,27 @@
assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(180 * MINUTE_IN_MS);
+ batteryUsageStats.close();
}
@Test
- public void batteryLevelInfo_charging() {
+ public void batteryLevelInfo_charging() throws IOException {
final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(true);
assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(1_200_000);
+ batteryUsageStats.close();
}
@Test
- public void batteryLevelInfo_onBattery() {
+ public void batteryLevelInfo_onBattery() throws IOException {
final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(600_000);
+ batteryUsageStats.close();
}
@Test
- public void test_selectPowerComponents() {
+ public void test_selectPowerComponents() throws IOException {
BatteryStatsImpl batteryStats = prepareBatteryStats(false);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
@@ -163,6 +166,8 @@
assertThat(
uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isEqualTo(0);
+
+ batteryUsageStats.close();
}
private BatteryStatsImpl prepareBatteryStats(boolean plugInAtTheEnd) {
@@ -274,7 +279,7 @@
}
@Test
- public void testWriteAndReadHistory() {
+ public void testWriteAndReadHistory() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
synchronized (batteryStats) {
batteryStats.setRecordAllHistoryLocked(true);
@@ -306,6 +311,8 @@
Parcel in = Parcel.obtain();
batteryUsageStats.writeToParcel(in, 0);
+ batteryUsageStats.close();
+
final byte[] bytes = in.marshall();
Parcel out = Parcel.obtain();
@@ -349,10 +356,12 @@
assertThat(iterator.hasNext()).isFalse();
assertThat(iterator.next()).isNull();
+
+ unparceled.close();
}
@Test
- public void testWriteAndReadHistoryTags() {
+ public void testWriteAndReadHistoryTags() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
synchronized (batteryStats) {
batteryStats.setRecordAllHistoryLocked(true);
@@ -400,6 +409,8 @@
assertThat(parcel.dataSize()).isAtMost(128_000);
}
+ batteryUsageStats.close();
+
parcel.setDataPosition(0);
BatteryUsageStats unparceled = parcel.readParcelable(getClass().getClassLoader(),
@@ -461,7 +472,7 @@
}
@Test
- public void testAggregateBatteryStats() {
+ public void testAggregateBatteryStats() throws IOException {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
setTime(5 * MINUTE_IN_MS);
@@ -563,6 +574,8 @@
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.1)
.of(180.0);
+
+ stats.close();
}
@Test
@@ -689,6 +702,8 @@
.isEqualTo(60 * MINUTE_IN_MS);
assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
+
+ stats.close();
}
private void setTime(long timeMs) {
@@ -733,6 +748,7 @@
.isWithin(PRECISION).of(8.33333);
assertThat(uid.getConsumedPower(componentId1))
.isWithin(PRECISION).of(8.33333);
+ stats.close();
return null;
}).when(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
@@ -750,7 +766,7 @@
}
@Test
- public void testAggregateBatteryStats_incompatibleSnapshot() {
+ public void testAggregateBatteryStats_incompatibleSnapshot() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
@@ -776,6 +792,8 @@
when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
.thenReturn(span1);
+ span1.close();
+
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
@@ -787,5 +805,7 @@
assertThat(stats.getCustomPowerComponentNames())
.isEqualTo(batteryStats.getCustomEnergyConsumerNames());
assertThat(stats.getStatsDuration()).isEqualTo(1234);
+
+ stats.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 52675f6..383616e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -323,6 +323,7 @@
}
private void before() {
+ BatteryUsageStats.DEBUG_INSTANCE_COUNT = true;
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
mThrowable = throwable;
@@ -338,6 +339,10 @@
private void after() throws Throwable {
waitForBackgroundThread();
+ if (mBatteryUsageStats != null) {
+ mBatteryUsageStats.close();
+ }
+ BatteryUsageStats.assertAllInstancesClosed();
}
public void waitForBackgroundThread() throws Throwable {
@@ -409,6 +414,14 @@
}
BatteryUsageStats apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
+ if (mBatteryUsageStats != null) {
+ try {
+ mBatteryUsageStats.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ mBatteryUsageStats = null;
+ }
final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 88624f2..1b6b8c4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -76,22 +76,25 @@
private static final int APP_UID2 = 314;
@Test
- public void testBuilder() {
+ public void testBuilder() throws Exception {
BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(true).build();
assertBatteryUsageStats1(batteryUsageStats, true);
+ batteryUsageStats.close();
}
@Test
- public void testBuilder_noProcessStateData() {
+ public void testBuilder_noProcessStateData() throws Exception {
BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(false).build();
assertBatteryUsageStats1(batteryUsageStats, false);
+ batteryUsageStats.close();
}
@Test
- public void testParcelability_smallNumberOfUids() {
+ public void testParcelability_smallNumberOfUids() throws Exception {
final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build();
final Parcel parcel = Parcel.obtain();
parcel.writeParcelable(outBatteryUsageStats, 0);
+ outBatteryUsageStats.close();
assertThat(parcel.dataSize()).isLessThan(100000);
@@ -101,10 +104,11 @@
parcel.readParcelable(getClass().getClassLoader());
assertThat(inBatteryUsageStats).isNotNull();
assertBatteryUsageStats1(inBatteryUsageStats, true);
+ inBatteryUsageStats.close();
}
@Test
- public void testParcelability_largeNumberOfUids() {
+ public void testParcelability_largeNumberOfUids() throws Exception {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[0]);
@@ -141,22 +145,27 @@
assertThat(uidBatteryConsumer).isNotNull();
assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(i * 100);
}
+ inBatteryUsageStats.close();
+ outBatteryUsageStats.close();
}
@Test
- public void testDefaultSessionDuration() {
+ public void testDefaultSessionDuration() throws Exception {
final BatteryUsageStats stats =
buildBatteryUsageStats1(true).setStatsDuration(10000).build();
assertThat(stats.getStatsDuration()).isEqualTo(10000);
+ stats.close();
}
@Test
- public void testDump() {
+ public void testDump() throws Exception {
final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
final StringWriter out = new StringWriter();
try (PrintWriter pw = new PrintWriter(out)) {
stats.dump(pw, " ");
}
+ stats.close();
+
final String dump = out.toString();
assertThat(dump).contains("Capacity: 4000");
@@ -187,12 +196,14 @@
}
@Test
- public void testDumpNoScreenOrPowerState() {
+ public void testDumpNoScreenOrPowerState() throws Exception {
final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build();
final StringWriter out = new StringWriter();
try (PrintWriter pw = new PrintWriter(out)) {
stats.dump(pw, " ");
}
+ stats.close();
+
final String dump = out.toString();
assertThat(dump).contains("Capacity: 4000");
@@ -222,7 +233,7 @@
}
@Test
- public void testAdd() {
+ public void testAdd() throws Exception {
final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
final BatteryUsageStats sum =
@@ -261,24 +272,31 @@
assertAggregateBatteryConsumer(sum,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
40211, 40422, 40633, 40844);
+ stats1.close();
+ stats2.close();
+ sum.close();
}
@Test
- public void testAdd_customComponentMismatch() {
+ public void testAdd_customComponentMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+ stats.close();
+ builder.discard();
}
@Test
- public void testAdd_processStateDataMismatch() {
+ public void testAdd_processStateDataMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+ stats.close();
+ builder.discard();
}
@Test
@@ -290,12 +308,14 @@
final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
stats.writeXml(serializer);
serializer.endDocument();
+ stats.close();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
TypedXmlPullParser parser = Xml.newBinaryPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
assertBatteryUsageStats1(fromXml, true);
+ fromXml.close();
}
private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1e4454c..b374a32 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.usage.NetworkStatsManager;
@@ -80,7 +81,7 @@
Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class),
- mock(UserInfoProvider.class), mock(PowerProfile.class),
+ mock(UserInfoProvider.class), mockPowerProfile(),
new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()),
powerStatsUidResolver, mock(FrameworkStatsLogger.class),
mock(BatteryStatsHistory.TraceDelegate.class),
@@ -96,6 +97,12 @@
mKernelWakelockReader = null;
}
+ private static PowerProfile mockPowerProfile() {
+ PowerProfile powerProfile = mock(PowerProfile.class);
+ when(powerProfile.getNumDisplays()).thenReturn(1);
+ return powerProfile;
+ }
+
public void initMeasuredEnergyStats(String[] customBucketNames) {
final boolean[] supportedStandardBuckets =
new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 7aa2e0f..2b55303 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,6 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -50,7 +53,8 @@
IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
@Mock
- private Context mContextSpy;
+ private Context mContext;
+ private BackupTransportConnection mBackupTransportConnection;
private ForensicService mForensicService;
private TestLooper mTestLooper;
@@ -63,7 +67,7 @@
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
- mForensicService = new ForensicService(new MockInjector(mContextSpy));
+ mForensicService = new ForensicService(new MockInjector(mContext));
mForensicService.onStart();
}
@@ -253,6 +257,8 @@
assertEquals(STATE_VISIBLE, scb1.mState);
assertEquals(STATE_VISIBLE, scb2.mState);
+ doReturn(true).when(mBackupTransportConnection).initialize();
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
mTestLooper.dispatchAll();
@@ -262,6 +268,29 @@
}
@Test
+ public void testEnable_FromVisible_TwoMonitors_BackupTransportUnavailable()
+ throws RemoteException {
+ mForensicService.setState(STATE_VISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+
+ doReturn(false).when(mBackupTransportConnection).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_BACKUP_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue());
+ }
+
+ @Test
public void testEnable_FromEnabled_TwoMonitors() throws RemoteException {
mForensicService.setState(STATE_ENABLED);
StateCallback scb1 = new StateCallback();
@@ -330,6 +359,8 @@
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
+ doNothing().when(mBackupTransportConnection).release();
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().disable(ccb);
mTestLooper.dispatchAll();
@@ -356,6 +387,12 @@
return mLooper;
}
+ @Override
+ public BackupTransportConnection getBackupTransportConnection() {
+ mBackupTransportConnection = spy(new BackupTransportConnection(mContext));
+ return mBackupTransportConnection;
+ }
+
}
private static class StateCallback extends IForensicServiceStateCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 7829fcc..8df18a8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -480,7 +480,7 @@
if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
mFullScreenMagnificationControllerStub.resetAndStubMethods();
mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
- config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
+ config.getCenterX(), config.getCenterY(), true, false, SERVICE_ID);
mMagnificationManagerStub.deactivateIfNeed();
} else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
mMagnificationManagerStub.resetAndStubMethods();
@@ -531,6 +531,9 @@
};
doAnswer(enableMagnificationStubAnswer).when(
mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
+ anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), eq(SERVICE_ID));
+ doAnswer(enableMagnificationStubAnswer).when(
+ mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
anyFloat(), anyFloat(), anyBoolean(), eq(SERVICE_ID));
Answer disableMagnificationStubAnswer = invocation -> {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index c4b4afd..5985abc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
@@ -76,6 +77,7 @@
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -126,6 +128,7 @@
final Resources mMockResources = mock(Resources.class);
final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+ final InputManagerInternal mMockInputManager = mock(InputManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
MagnificationAnimationCallback.class);
private final MagnificationInfoChangedCallback mRequestObserver = mock(
@@ -163,6 +166,7 @@
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+ when(mMockControllerCtx.getInputManager()).thenReturn(mMockInputManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
mResolver = new MockContentResolver();
@@ -285,10 +289,11 @@
mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
100));
assertFalse(mFullScreenMagnificationController.reset(displayId, true));
- assertFalse(mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(
+ mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, true, 0));
assertFalse(mFullScreenMagnificationController.setCenter(displayId, 100, 100, false, 1));
assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- 1.5f, 100, 100, false, 2));
+ 1.5f, 100, 100, true, false, 2));
assertTrue(mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
mFullScreenMagnificationController.getMagnificationRegion(displayId, new Region());
@@ -313,7 +318,7 @@
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertFalse(mFullScreenMagnificationController
- .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, true, false, SERVICE_ID_1));
assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
}
@@ -331,7 +336,7 @@
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(displayId, scale, center.x, center.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
@@ -357,7 +362,7 @@
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+ .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// New center should be halfway between original center and pivot
@@ -405,7 +410,7 @@
float scale = 2.0f;
assertTrue(mFullScreenMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
@@ -440,7 +445,7 @@
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
- newCenter.x, newCenter.y, mAnimationCallback, SERVICE_ID_1));
+ newCenter.x, newCenter.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -486,11 +491,11 @@
final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
final float targetScale = 2.0f;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- targetScale, center.x, center.y, false, SERVICE_ID_1));
+ targetScale, center.x, center.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- targetScale, center.x, center.y, mAnimationCallback, SERVICE_ID_1));
+ targetScale, center.x, center.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
@@ -516,7 +521,7 @@
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
MagnificationScaleProvider.MAX_SCALE + 1.0f,
- newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ newCenter.x, newCenter.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -527,7 +532,7 @@
// Verify that we can't zoom below 1x
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
@@ -551,7 +556,7 @@
// Off the edge to the top and left
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, -100f, -200f, false, SERVICE_ID_1));
+ scale, -100f, -200f, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
@@ -565,7 +570,7 @@
// Off the edge to the bottom and right
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
@@ -619,7 +624,7 @@
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockWindowManager);
@@ -673,7 +678,7 @@
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+ .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, true, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
@@ -685,7 +690,7 @@
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+ .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, true, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
@@ -710,7 +715,7 @@
float scale = 2.0f;
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -753,7 +758,7 @@
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -786,12 +791,12 @@
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
assertEquals(SERVICE_ID_1,
mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_2));
assertEquals(SERVICE_ID_2,
mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
@@ -809,10 +814,10 @@
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
mFullScreenMagnificationController
- .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1);
mFullScreenMagnificationController
- .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_2);
assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
@@ -873,7 +878,7 @@
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, firstCenter.x, firstCenter.y, mAnimationCallback, SERVICE_ID_1));
+ scale, firstCenter.x, firstCenter.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockValueAnimator);
// Stubs the logic after the animation is started.
@@ -1076,7 +1081,7 @@
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- true, SERVICE_ID_1);
+ true, true, SERVICE_ID_1);
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
@@ -1107,7 +1112,7 @@
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
@@ -1148,7 +1153,7 @@
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- true, SERVICE_ID_1);
+ true, true, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
@@ -1335,7 +1340,7 @@
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
+ scale, firstCenter.x, firstCenter.y, true, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(firstCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -1404,7 +1409,7 @@
register(DISPLAY_0);
final float scale = 1.0f;
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
+ DISPLAY_0, scale, Float.NaN, Float.NaN, true, true, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
verify(mMockWindowManager).setFullscreenMagnificationActivated(DISPLAY_0, true);
@@ -1449,7 +1454,7 @@
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
mFullScreenMagnificationController.setScale(TEST_DISPLAY, 1.0f, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
// persistScale may post a task to a background thread. Let's wait for it completes.
@@ -1464,10 +1469,10 @@
register(DISPLAY_1);
final PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
mFullScreenMagnificationController.setScale(DISPLAY_0, 3.0f, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(DISPLAY_0);
mFullScreenMagnificationController.setScale(DISPLAY_1, 4.0f, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(DISPLAY_1);
// persistScale may post a task to a background thread. Let's wait for it completes.
@@ -1479,6 +1484,101 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void persistScale_setValue_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+ anyFloat());
+
+ mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
+ 4.0f);
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void setScale_setNonTransientScale_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+ /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void setScaleAndCenter_setTransientScale_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+ point.y, /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mRequestObserver).onFullScreenMagnificationChanged(anyInt(), any(Region.class),
+ any(MagnificationConfig.class));
+ verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+ anyFloat());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void setScaleAndCenter_setNonTransientScale_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+ point.y, /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 3.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void setCenter_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.setCenter(TEST_DISPLAY, point.x, point.y, false,
+ SERVICE_ID_1);
+
+ // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+ // manager, but we currently do. The input manager skips redundant computation if the
+ // notified scale is the same as the previous call.
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+ 2.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
+ public void offsetMagnifiedRegion_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.offsetMagnifiedRegion(TEST_DISPLAY, 100, 50,
+ SERVICE_ID_1);
+
+ // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+ // manager, but we currently do. The input manager skips redundant computation if the
+ // notified scale is the same as the previous call.
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+ 2.0f);
+ }
+
+ @Test
public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
setScaleToMagnifying();
@@ -1535,7 +1635,7 @@
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
}
private void initMockWindowManager() {
@@ -1578,7 +1678,7 @@
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index e5831b3..9f5dd93 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -90,6 +90,7 @@
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
+import com.android.server.input.InputManagerInternal;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.WindowManagerInternal;
@@ -227,9 +228,11 @@
final FullScreenMagnificationController.ControllerContext mockController =
mock(FullScreenMagnificationController.ControllerContext.class);
final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+ final InputManagerInternal mockInputManager = mock(InputManagerInternal.class);
when(mockController.getContext()).thenReturn(mContext);
when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+ when(mockController.getInputManager()).thenReturn(mockInputManager);
when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
@@ -1343,7 +1346,7 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1364,7 +1367,7 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1401,7 +1404,7 @@
mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1438,7 +1441,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
@@ -1530,7 +1533,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 6.2f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, InputDevice.SOURCE_MOUSE);
fastForward(20);
@@ -1571,7 +1574,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
// HOVER_MOVE should change magnifier viewport.
MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
@@ -1615,7 +1618,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 5.3f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, source);
fastForward(20);
@@ -1649,7 +1652,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 2.7f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, source);
fastForward(20);
@@ -1685,7 +1688,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 3.8f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
@@ -1722,7 +1725,7 @@
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 2528177..8164ef9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -76,6 +76,7 @@
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.window.flags.Flags;
@@ -154,6 +155,8 @@
private WindowManagerInternal mWindowManagerInternal;
@Mock
private WindowManagerInternal.AccessibilityControllerInternal mA11yController;
+ @Mock
+ private InputManagerInternal mInputManagerInternal;
@Mock
private DisplayManagerInternal mDisplayManagerInternal;
@@ -200,6 +203,7 @@
when(mControllerCtx.getContext()).thenReturn(mContext);
when(mControllerCtx.getTraceManager()).thenReturn(mTraceManager);
when(mControllerCtx.getWindowManager()).thenReturn(mWindowManagerInternal);
+ when(mControllerCtx.getInputManager()).thenReturn(mInputManagerInternal);
when(mControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mControllerCtx.getAnimationDuration()).thenReturn(1000L);
when(mControllerCtx.newValueAnimator()).thenReturn(mValueAnimator);
@@ -417,7 +421,7 @@
assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY,
DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
- true, MAGNIFICATION_GESTURE_HANDLER_ID);
+ true, true, MAGNIFICATION_GESTURE_HANDLER_ID);
verify(mTransitionCallBack).onResult(TEST_DISPLAY, false);
}
@@ -467,7 +471,7 @@
assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
eq(DEFAULT_SCALE), eq(MAGNIFIED_CENTER_X), eq(MAGNIFIED_CENTER_Y),
- any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
+ eq(false), any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
}
@Test
@@ -484,7 +488,7 @@
verify(mScreenMagnificationController, never()).setScaleAndCenter(anyInt(),
anyFloat(), anyFloat(), anyFloat(),
- anyBoolean(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt());
}
@Test
@@ -546,7 +550,7 @@
config, animate, TEST_SERVICE_ID);
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
/* scale= */ anyFloat(), /* centerX= */ anyFloat(), /* centerY= */ anyFloat(),
- mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
+ anyBoolean(), mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
mCallbackArgumentCaptor.getValue().onResult(true);
mMockConnection.invokeCallbacks();
@@ -616,7 +620,7 @@
@Test
public void magnifyThroughExternalRequest_showMagnificationButton() {
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE,
- MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID);
+ MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, false, TEST_SERVICE_ID);
// The first time is trigger when fullscreen mode is activated.
// The second time is triggered when magnification spec is changed.
@@ -638,7 +642,7 @@
mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(newScale),
- anyFloat(), anyFloat(), anyBoolean(), anyInt());
+ anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), anyInt());
verify(mScreenMagnificationController).persistScale(eq(TEST_DISPLAY));
}
@@ -681,7 +685,7 @@
final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
config.getScale(), config.getCenterX(), config.getCenterY(),
- true, TEST_SERVICE_ID);
+ true, true, TEST_SERVICE_ID);
// The notify method is triggered when setting magnification enabled.
// The setScaleAndCenter call should not trigger notify method due to same scale and center.
@@ -930,7 +934,7 @@
public void onWindowModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() {
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
- true, TEST_SERVICE_ID);
+ true, true, TEST_SERVICE_ID);
mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
@@ -1317,7 +1321,8 @@
}
if (mode == MODE_FULLSCREEN) {
mScreenMagnificationController.setScaleAndCenter(displayId, DEFAULT_SCALE, centerX,
- centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ centerY, true, true,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
} else {
mMagnificationConnectionManager.enableWindowMagnification(displayId, DEFAULT_SCALE,
centerX, centerY, null, TEST_SERVICE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 9c6412b..a2e6d4c 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -191,98 +191,6 @@
}
@Test
- public void updateRuleSet_notAuthorized() throws Exception {
- makeUsSystemApp();
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
- TestUtils.assertExpectException(
- SecurityException.class,
- "Only system packages specified in config_integrityRuleProviderPackages are"
- + " allowed to call this method.",
- () ->
- mService.updateRuleSet(
- VERSION,
- new ParceledListSlice<>(Arrays.asList(rule)),
- /* statusReceiver= */ null));
- }
-
- @Test
- public void updateRuleSet_notSystemApp() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp(false);
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
- TestUtils.assertExpectException(
- SecurityException.class,
- "Only system packages specified in config_integrityRuleProviderPackages are"
- + " allowed to call this method.",
- () ->
- mService.updateRuleSet(
- VERSION,
- new ParceledListSlice<>(Arrays.asList(rule)),
- /* statusReceiver= */ null));
- }
-
- @Test
- public void updateRuleSet_authorized() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp();
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
-
- // no SecurityException
- mService.updateRuleSet(
- VERSION, new ParceledListSlice<>(Arrays.asList(rule)), mock(IntentSender.class));
- }
-
- @Test
- public void updateRuleSet_correctMethodCall() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp();
- IntentSender mockReceiver = mock(IntentSender.class);
- List<Rule> rules =
- Arrays.asList(
- new Rule(
- IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
- Rule.DENY));
-
- mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
- runJobInHandler();
-
- verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
- ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
- assertEquals(STATUS_SUCCESS, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
- }
-
- @Test
- public void updateRuleSet_fail() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp();
- doThrow(new IOException()).when(mIntegrityFileManager).writeRules(any(), any(), any());
- IntentSender mockReceiver = mock(IntentSender.class);
- List<Rule> rules =
- Arrays.asList(
- new Rule(
- IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
- Rule.DENY));
-
- mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
- runJobInHandler();
-
- verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
- ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
- assertEquals(STATUS_FAILURE, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
- }
-
- @Test
public void broadcastReceiverRegistration() throws Exception {
allowlistUsAsRuleProvider();
makeUsSystemApp();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 592eec5..bc01fc4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -260,9 +260,10 @@
}
private void initAttentionHelper(TestableFlagResolver flagResolver) {
- mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
- mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
- mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAttentionHelper = new NotificationAttentionHelper(getContext(), new Object(),
+ mock(LightsManager.class),mAccessibilityManager, mPackageManager,
+ mUserManager, mUsageStats, mService.mNotificationManagerPrivate,
+ mock(ZenModeHelper.class), flagResolver);
mAttentionHelper.onSystemReady();
mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
mAttentionHelper.setAudioManager(mAudioManager);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index ff2ce15..6dba967 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -41,6 +41,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
@@ -62,18 +64,23 @@
final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
| SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
final var data = new byte[] {0x11, 0x22};
- final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
- keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+ final var keyphrases = new ArrayList<SoundTrigger.KeyphraseRecognitionExtra>(2);
+ keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(99,
RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
- new ConfidenceLevel(5000, 80)});
- keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+ new ConfidenceLevel(5000, 80)}));
+ keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(101,
RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
new ConfidenceLevel(7777, 30),
- new ConfidenceLevel(2222, 60)});
+ new ConfidenceLevel(2222, 60)}));
- var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
- keyphrases, data, flags);
+ var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
+ .setCaptureRequested(true)
+ .setAllowMultipleTriggers(false) // must be false
+ .setKeyphrases(keyphrases)
+ .setData(data)
+ .setAudioCapabilities(flags)
+ .build();
assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c30b4bb..c3466b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -139,7 +139,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.provider.DeviceConfig;
import android.util.MutableBoolean;
import android.view.DisplayInfo;
@@ -2662,8 +2661,11 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT)
public void testSetOrientation_restrictedByTargetSdk() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ makeDisplayLargeScreen(mDisplayContent);
+
assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false);
assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
@@ -2673,12 +2675,13 @@
}
private void assertSetOrientation(int targetSdk, int category, boolean expectRotate) {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity.mTargetSdk = targetSdk;
+ final String packageName = targetSdk <= Build.VERSION_CODES.VANILLA_ICE_CREAM
+ ? mContext.getPackageName() // WmTests uses legacy sdk.
+ : null; // Simulate CUR_DEVELOPMENT by invalid package (see PlatformCompat).
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setComponent(getUniqueComponentName(packageName)).build();
activity.info.applicationInfo.category = category;
- activity.setVisible(true);
-
// Assert orientation is unspecified to start.
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index c8a3559..08963f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -307,6 +307,8 @@
void createNewTaskWithBaseActivity() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
+ // Respect "@ChangeId" according to test package's target sdk.
+ .setPackage(mAtm.mContext.getPackageName())
.setDisplay(mDisplayContent).build();
mTaskStack.push(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5c0d424..2bebcc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1081,7 +1081,8 @@
final DisplayRotation dr = dc.getDisplayRotation();
spyOn(dr);
doReturn(false).when(dr).useDefaultSettingsProvider();
- final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setComponent(getUniqueComponentName(mContext.getPackageName())).build();
app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app);
assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 35c9e3f..f4fa12e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -51,6 +51,7 @@
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -592,6 +593,11 @@
.setTask(mTask)
.build();
+ spyOn(mActivity.info.applicationInfo);
+ // Disable for camera compat.
+ doReturn(false).when(mActivity.info.applicationInfo).isChangeEnabled(
+ ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+
spyOn(mActivity.mAtmService.getLifecycleManager());
spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index cf1dcd0..7e8bd38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -285,6 +285,52 @@
}
@Test
+ public void testTaskLayerRankFreeform() {
+ mSetFlagsRule.enableFlags(com.android.window.flags.Flags
+ .FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE);
+ final Task[] freeformTasks = new Task[3];
+ final WindowProcessController[] processes = new WindowProcessController[3];
+ for (int i = 0; i < freeformTasks.length; i++) {
+ freeformTasks[i] = new TaskBuilder(mSupervisor)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).setCreateActivity(true).build();
+ final ActivityRecord r = freeformTasks[i].getTopMostActivity();
+ r.setState(RESUMED, "test");
+ processes[i] = r.app;
+ }
+ resizeDisplay(mDisplayContent, 2400, 2000);
+ // ---------
+ // | 2 | 1 |
+ // ---------
+ // | 0 | |
+ // ---------
+ freeformTasks[2].setBounds(0, 0, 1000, 1000);
+ freeformTasks[1].setBounds(1000, 0, 2000, 1000);
+ freeformTasks[0].setBounds(0, 1000, 1000, 2000);
+ mRootWindowContainer.rankTaskLayers();
+ assertEquals(1, freeformTasks[2].mLayerRank);
+ assertEquals(2, freeformTasks[1].mLayerRank);
+ assertEquals(3, freeformTasks[0].mLayerRank);
+ assertFalse("Top doesn't need perceptible hint", (processes[2].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ assertTrue((processes[1].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ assertTrue((processes[0].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+
+ // Make task2 occlude half of task0.
+ clearInvocations(mAtm);
+ freeformTasks[2].setBounds(0, 0, 1000, 1500);
+ waitHandlerIdle(mWm.mH);
+ // The process of task0 will demote from perceptible to visible.
+ final int stateFlags0 = processes[0].getActivityStateFlags();
+ assertTrue((stateFlags0
+ & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0);
+ assertFalse((stateFlags0
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ verify(mAtm).updateOomAdj();
+ }
+
+ @Test
public void testForceStopPackage() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e956e22..e66dfeb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4824,12 +4824,7 @@
assertFalse(mActivity.isUniversalResizeable());
mDisplayContent.setIgnoreOrientationRequest(true);
- final int swDp = mDisplayContent.getConfiguration().smallestScreenWidthDp;
- if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
- final int height = 100 + (int) (mDisplayContent.getDisplayMetrics().density
- * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
- resizeDisplay(mDisplayContent, 100 + height, height);
- }
+ makeDisplayLargeScreen(mDisplayContent);
assertTrue(mActivity.isUniversalResizeable());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a215c0a..757c358 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1123,6 +1123,15 @@
displayContent.onRequestedOverrideConfigurationChanged(c);
}
+ static void makeDisplayLargeScreen(DisplayContent displayContent) {
+ final int swDp = displayContent.getConfiguration().smallestScreenWidthDp;
+ if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+ final int height = 100 + (int) (displayContent.getDisplayMetrics().density
+ * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+ resizeDisplay(displayContent, 100 + height, height);
+ }
+ }
+
/** Used for the tests that assume the display is portrait by default. */
static void makeDisplayPortrait(DisplayContent displayContent) {
if (displayContent.mBaseDisplayHeight <= displayContent.mBaseDisplayWidth) {
@@ -1223,7 +1232,14 @@
}
static ComponentName getUniqueComponentName() {
- return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+ return getUniqueComponentName(DEFAULT_COMPONENT_PACKAGE_NAME);
+ }
+
+ static ComponentName getUniqueComponentName(String packageName) {
+ if (packageName == null) {
+ packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
+ }
+ return ComponentName.createRelative(packageName,
DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
}
@@ -1298,8 +1314,7 @@
ActivityBuilder setActivityTheme(int theme) {
mActivityTheme = theme;
// Use the real package of test so it can get a valid context for theme.
- mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
- DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+ mComponent = getUniqueComponentName(mService.mContext.getPackageName());
return this;
}
@@ -1743,7 +1758,7 @@
if (mIntent == null) {
mIntent = new Intent();
if (mComponent == null) {
- mComponent = getUniqueComponentName();
+ mComponent = getUniqueComponentName(mPackage);
}
mIntent.setComponent(mComponent);
mIntent.setFlags(mFlags);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 44de65a..79b3a7c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -582,6 +582,22 @@
"android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
/**
+ * Meta-data represents whether the application supports P2P SMS over carrier roaming satellite
+ * which needs manual trigger to connect to satellite. The messaging applications that supports
+ * P2P SMS over carrier roaming satellites should add the following in their AndroidManifest.
+ * {@code
+ * <application
+ * <meta-data
+ * android:name="android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT"
+ * android:value="true"/>
+ * </application>
+ * }
+ * @hide
+ */
+ public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT =
+ "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
+
+ /**
* Request to enable or disable the satellite modem and demo mode.
* If satellite modem and cellular modem cannot work concurrently,
* then this will disable the cellular modem if satellite modem is enabled,
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index c6855b4..4ac567c 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -285,7 +285,11 @@
val displayRect = getDisplayRect(wmHelper)
- val endX = if (isLeft) displayRect.left else displayRect.right
+ val endX = if (isLeft) {
+ displayRect.left + SNAP_RESIZE_DRAG_INSET
+ } else {
+ displayRect.right - SNAP_RESIZE_DRAG_INSET
+ }
val endY = displayRect.centerY() / 2
// drag the window to snap resize
@@ -391,6 +395,7 @@
private companion object {
val TIMEOUT: Duration = Duration.ofSeconds(3)
+ const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
const val CAPTION: String = "desktop_mode_caption"
const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
const val MAXIMIZE_MENU: String = "maximize_menu"
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 6742cbe..168141b 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -41,6 +41,7 @@
"hamcrest-library",
"junit-params",
"kotlin-test",
+ "mockito-kotlin-nodeps",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"platform-screenshot-diff-core",
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 7526737..787ae06 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -799,6 +799,27 @@
}
@Test
+ @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun testMoveToNextDisplay() {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "META + CTRL + D -> Move a task to next display",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_D
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ intArrayOf(KeyEvent.KEYCODE_D),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
fun testCapsLockPressNotified() {
val keyGestureController = KeyGestureController(context, testLooper.looper)
val listener = KeyGestureEventListener()
diff --git a/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
new file mode 100644
index 0000000..47e7ac7
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for {@link PointerIconCache}.
+ */
+@Presubmit
+class PointerIconCacheTest {
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ @Mock
+ private lateinit var defaultDisplay: Display
+
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+ private lateinit var cache: PointerIconCache
+
+ @Before
+ fun setup() {
+ whenever(defaultDisplay.displayId).thenReturn(Display.DEFAULT_DISPLAY)
+
+ context = object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) {
+ override fun getDisplay() = defaultDisplay
+ }
+
+ testLooper = TestLooper()
+ cache = PointerIconCache(context, native, Handler(testLooper.looper))
+ }
+
+ @Test
+ fun testSetPointerScale() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setPointerScale(2f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 2, bitmap.height)
+ assertEquals(defaultBitmap.width * 2, bitmap.width)
+ }
+
+ @Test
+ fun testSetAccessibilityScaleFactor() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 4f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 4, bitmap.height)
+ assertEquals(defaultBitmap.width * 4, bitmap.width)
+ }
+
+ @Test
+ fun testSetAccessibilityScaleFactorOnSecondaryDisplay() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ val secondaryDisplayId = Display.DEFAULT_DISPLAY + 1
+ cache.setAccessibilityScaleFactor(secondaryDisplayId, 4f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+ assertEquals(defaultBitmap.height, bitmap.height)
+ assertEquals(defaultBitmap.width, bitmap.width)
+
+ val bitmapSecondary =
+ cache.getLoadedPointerIcon(secondaryDisplayId, PointerIcon.TYPE_ARROW).bitmap
+ assertEquals(defaultBitmap.height * 4, bitmapSecondary.height)
+ assertEquals(defaultBitmap.width * 4, bitmapSecondary.width)
+ }
+
+ @Test
+ fun testSetPointerScaleAndAccessibilityScaleFactor() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setPointerScale(2f)
+ cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 3f)
+
+ testLooper.dispatchAll()
+ verify(native, times(2)).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 6, bitmap.height)
+ assertEquals(defaultBitmap.width * 6, bitmap.width)
+ }
+
+ private fun getDefaultIcon() =
+ PointerIcon.getLoadedSystemIcon(context, PointerIcon.TYPE_ARROW, false, 1f)
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
similarity index 96%
rename from tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 6f3deab..2692e12 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -40,7 +40,6 @@
import android.tools.traces.monitors.PerfettoTraceMonitor;
import android.tools.traces.protolog.ProtoLogTrace;
import android.tracing.perfetto.DataSource;
-import android.util.proto.ProtoInputStream;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -74,7 +73,7 @@
@SuppressWarnings("ConstantConditions")
@Presubmit
@RunWith(JUnit4.class)
-public class PerfettoProtoLogImplTest {
+public class ProcessedPerfettoProtoLogImplTest {
private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb";
private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
@@ -100,7 +99,7 @@
private static ProtoLogViewerConfigReader sReader;
- public PerfettoProtoLogImplTest() throws IOException {
+ public ProcessedPerfettoProtoLogImplTest() throws IOException {
}
@BeforeClass
@@ -151,7 +150,8 @@
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
ViewerConfigInputStreamProvider.class);
Mockito.when(viewerConfigInputStreamProvider.getInputStream())
- .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()));
+ .thenAnswer(it -> new AutoClosableProtoInputStream(
+ sViewerConfigBuilder.build().toByteArray()));
sCacheUpdater = () -> {};
sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
@@ -165,21 +165,16 @@
throw new RuntimeException(
"Unexpected viewer config file path provided");
}
- return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+ return new AutoClosableProtoInputStream(sViewerConfigBuilder.build().toByteArray());
});
};
sProtoLogConfigurationService =
new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
- if (android.tracing.Flags.clientSideProtoLogging()) {
- sProtoLog = new PerfettoProtoLogImpl(
- MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(),
- TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
- } else {
- sProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
- TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
- }
+ sProtoLog = new ProcessedPerfettoProtoLogImpl(
+ MOCK_VIEWER_CONFIG_FILE, viewerConfigInputStreamProvider, sReader,
+ () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder,
+ sProtoLogConfigurationService);
busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME);
}
@@ -398,18 +393,17 @@
}
@Test
- public void log_logcatEnabledNoMessage() {
+ public void log_logcatEnabledNoMessageThrows() {
when(sReader.getViewerString(anyLong())).thenReturn(null);
PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
- implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
- new Object[]{5});
-
- verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
- verify(sReader).getViewerString(eq(1234L));
+ var assertion = assertThrows(RuntimeException.class, () ->
+ implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5}));
+ Truth.assertThat(assertion).hasMessageThat()
+ .contains("Failed to decode message for logcat");
}
@Test
@@ -539,16 +533,12 @@
PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
.enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
.build();
- long before;
- long after;
try {
traceMonitor.start();
- before = SystemClock.elapsedRealtimeNanos();
sProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
0b01100100,
new Object[]{"test", 1, 0.1, true});
- after = SystemClock.elapsedRealtimeNanos();
} finally {
traceMonitor.stop(mWriter);
}
@@ -606,7 +596,8 @@
Truth.assertThat(stacktrace).doesNotContain(DataSource.class.getSimpleName() + ".java");
Truth.assertThat(stacktrace)
.doesNotContain(ProtoLogImpl.class.getSimpleName() + ".java");
- Truth.assertThat(stacktrace).contains(PerfettoProtoLogImplTest.class.getSimpleName());
+ Truth.assertThat(stacktrace)
+ .contains(ProcessedPerfettoProtoLogImplTest.class.getSimpleName());
Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 8ecddaa..3d1e208 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -47,12 +47,12 @@
}
@Test
- public void throwOnRegisteringDuplicateGroup() {
- final var assertion = assertThrows(RuntimeException.class,
- () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2));
+ public void deduplicatesRegisteringDuplicateGroup() {
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2);
- Truth.assertThat(assertion).hasMessageThat().contains("" + TEST_GROUP_1.getId());
- Truth.assertThat(assertion).hasMessageThat().contains("duplicate");
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
}
@Test
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index d78ced1..9e029a8 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -19,9 +19,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import android.os.Build;
import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
+import com.google.common.truth.Truth;
+
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,6 +32,8 @@
import perfetto.protos.ProtologCommon;
+import java.io.File;
+
@Presubmit
@RunWith(JUnit4.class)
public class ProtoLogViewerConfigReaderTest {
@@ -83,7 +88,7 @@
).build().toByteArray();
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
- () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+ () -> new AutoClosableProtoInputStream(TEST_VIEWER_CONFIG);
private ProtoLogViewerConfigReader mConfig;
@@ -123,6 +128,31 @@
}
@Test
+ public void viewerConfigIsOnDevice() {
+ Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric"));
+
+ final String[] viewerConfigPaths;
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ viewerConfigPaths = new String[] {
+ "/system_ext/etc/wmshell.protolog.pb",
+ "/system/etc/core.protolog.pb",
+ };
+ } else {
+ viewerConfigPaths = new String[] {
+ "/system_ext/etc/wmshell.protolog.json.gz",
+ "/system/etc/protolog.conf.json.gz",
+ };
+ }
+
+ for (final var viewerConfigPath : viewerConfigPaths) {
+ File f = new File(viewerConfigPath);
+
+ Truth.assertWithMessage(f.getAbsolutePath() + " exists").that(f.exists()).isTrue();
+ }
+
+ }
+
+ @Test
public void loadUnloadAndReloadViewerConfig() {
loadViewerConfig();
unloadViewerConfig();
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
index caa018d..e163ef4 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
@@ -485,6 +485,7 @@
"android.net.thread.IConfigurationReceiver",
"android.net.thread.IOperationalDatasetCallback",
"android.net.thread.IOperationReceiver",
+ "android.net.thread.IOutputReceiver",
"android.net.thread.IStateCallback",
"android.net.thread.IThreadNetworkController",
"android.net.thread.IThreadNetworkManager",
@@ -757,6 +758,7 @@
"com.android.server.thread.openthread.IChannelMasksReceiver",
"com.android.server.thread.openthread.INsdPublisher",
"com.android.server.thread.openthread.IOtDaemonCallback",
+ "com.android.server.thread.openthread.IOtOutputReceiver",
"com.android.server.thread.openthread.IOtStatusReceiver",
"com.google.android.clockwork.ambient.offload.IDisplayOffloadService",
"com.google.android.clockwork.ambient.offload.IDisplayOffloadTransitionFinishedCallbacks",