diff options
260 files changed, 8678 insertions, 6300 deletions
diff --git a/Android.bp b/Android.bp index 9d05ffd3de8c..73eea8c88837 100644 --- a/Android.bp +++ b/Android.bp @@ -26,48 +26,166 @@ // READ ME: ######################################################## filegroup { - name: "framework-defaults-java-srcs", + name: "framework-core-sources", srcs: [ - // java sources under this directory "core/java/**/*.java", + "core/java/**/*.aidl", + ], + path: "core/java", +} + +filegroup { + name: "framework-drm-sources", + srcs: [ "drm/java/**/*.java", + ], + path: "drm/java", +} + +filegroup { + name: "framework-graphics-sources", + srcs: [ "graphics/java/**/*.java", + "graphics/java/**/*.aidl", + ], + path: "graphics/java", +} + +filegroup { + name: "framework-keystore-sources", + srcs: [ "keystore/java/**/*.java", + "keystore/java/**/*.aidl", + ], + path: "keystore/java", +} + +filegroup { + name: "framework-location-sources", + srcs: [ "location/java/**/*.java", + "location/java/**/*.aidl", + ], + path: "location/java", +} + +filegroup { + name: "framework-lowpan-sources", + srcs: [ "lowpan/java/**/*.java", + "lowpan/java/**/*.aidl", + ], + path: "lowpan/java", +} + +filegroup { + name: "framework-media-sources", + srcs: [ "media/java/**/*.java", + "media/java/**/*.aidl", + ], + path: "media/java", +} + +filegroup { + name: "framework-mca-effect-sources", + srcs: [ "media/mca/effect/java/**/*.java", + ], + path: "media/mca/effect/java", +} + +filegroup { + name: "framework-mca-filterfw-sources", + srcs: [ "media/mca/filterfw/java/**/*.java", + ], + path: "media/mca/filterfw/java", +} + +filegroup { + name: "framework-mca-filterpacks-sources", + srcs: [ "media/mca/filterpacks/java/**/*.java", + ], + path: "media/mca/filterpacks/java", +} + +filegroup { + name: "framework-opengl-sources", + srcs: [ "opengl/java/**/*.java", + ], + path: "opengl/java", +} + +filegroup { + name: "framework-rs-sources", + srcs: [ "rs/java/**/*.java", + ], + path: "rs/java", +} + +filegroup { + name: "framework-sax-sources", + srcs: [ "sax/java/**/*.java", + ], + path: "sax/java", +} + +filegroup { + name: "framework-telecomm-sources", + srcs: [ "telecomm/java/**/*.java", + "telecomm/java/**/*.aidl", + ], + path: "telecomm/java", +} + +filegroup { + name: "framework-telephony-sources", + srcs: [ "telephony/java/**/*.java", + "telephony/java/**/*.aidl", + ], + path: "telephony/java", +} + +filegroup { + name: "framework-wifi-sources", + srcs: [ "wifi/java/**/*.java", + "wifi/java/**/*.aidl", ], + path: "wifi/java", } -// TODO(b/70046217): make these as filegroups where the base directory for aidl files -// is given as 'path'. Eliminate the need for aidl_local_include_dirs. framework_srcs = [ - // aidl under this directory - // b/70046217#comment15 These MUST come after all java srcs. - // TODO(b/70046217) remove the above requirement - "core/java/**/*.aidl", - "graphics/java/**/*.aidl", - "keystore/java/**/*.aidl", - "location/java/**/*.aidl", - "lowpan/java/**/*.aidl", - "media/java/**/*.aidl", - "packages/services/PacProcessor/**/*.aidl", - "packages/services/Proxy/**/*.aidl", - "telecomm/java/**/*.aidl", - "telephony/java/**/*.aidl", - "wifi/java/**/*.aidl", - - // aidl from external directories + // Java/AIDL sources under frameworks/base + ":framework-core-sources", + ":framework-drm-sources", + ":framework-graphics-sources", + ":framework-keystore-sources", + ":framework-location-sources", + ":framework-lowpan-sources", + ":framework-media-sources", + ":framework-mca-effect-sources", + ":framework-mca-filterfw-sources", + ":framework-mca-filterpacks-sources", + ":framework-opengl-sources", + ":framework-rs-sources", + ":framework-sax-sources", + ":framework-telecomm-sources", + ":framework-telephony-sources", + ":framework-wifi-sources", + ":PacProcessor-aidl-sources", + ":ProxyHandler-aidl-sources", + + // AIDL sources from external directories ":dumpstate_aidl", + ":framework_native_aidl", ":gatekeeper_aidl", ":gsiservice_aidl", ":incidentcompanion_aidl", @@ -88,47 +206,28 @@ framework_srcs = [ ":framework-statslog-gen", ] -framework_aidl_local_include_dirs = [ - "core/java", - "drm/java", - "graphics/java", - "keystore/java", - "location/java", - "lowpan/java", - "media/java", - "media/apex/java", - "media/mca/effect/java", - "media/mca/filterfw/java", - "media/mca/filterpacks/java", - "opengl/java", - "rs/java", - "sax/java", - "telecomm/java", - "telephony/java", - "wifi/java", -] - -framework_aidl_external_include_dirs = [ - "frameworks/av/camera/aidl", - "frameworks/av/media/libaudioclient/aidl", - "frameworks/native/aidl/binder", - "frameworks/native/aidl/gui", - "frameworks/native/cmds/dumpstate/binder", - "frameworks/native/libs/incidentcompanion/binder", - "system/bt/binder", - "system/core/gatekeeperd/binder", - "system/core/storaged/binder", - "system/gsid/aidl", - "system/security/keystore/binder", - "system/update_engine/binder_bindings", - "system/vold/binder", -] - java_defaults { name: "framework-aidl-export-defaults", - aidl: { - export_include_dirs: framework_aidl_local_include_dirs, + export_include_dirs: [ + "core/java", + "drm/java", + "graphics/java", + "keystore/java", + "location/java", + "lowpan/java", + "media/java", + "media/apex/java", + "media/mca/effect/java", + "media/mca/filterfw/java", + "media/mca/filterpacks/java", + "opengl/java", + "rs/java", + "sax/java", + "telecomm/java", + "telephony/java", + "wifi/java", + ], }, } @@ -137,13 +236,9 @@ java_defaults { defaults: ["framework-aidl-export-defaults"], installable: true, - srcs: [ - ":framework-defaults-java-srcs", - ] + framework_srcs, + srcs: framework_srcs, aidl: { - local_include_dirs: framework_aidl_local_include_dirs, - include_dirs: framework_aidl_external_include_dirs, generate_get_transaction_name: true, }, @@ -261,6 +356,7 @@ java_library { required: [ "framework-platform-compat-config", "libcore-platform-compat-config", + "services-platform-compat-config", ], sdk_version: "core_platform", } @@ -792,7 +888,6 @@ stubs_defaults { ":jobscheduler-framework-source", ], srcs_lib: "framework-minus-apex", - srcs_lib_whitelist_dirs: frameworks_base_subdirs, srcs_lib_whitelist_pkgs: packages_to_document, libs: framework_docs_only_libs, local_sourcepaths: frameworks_base_subdirs, @@ -850,7 +945,6 @@ stubs_defaults { ":jobscheduler-framework-source", ], srcs_lib: "framework-minus-apex", - srcs_lib_whitelist_dirs: frameworks_base_subdirs, srcs_lib_whitelist_pkgs: packages_to_document, local_sourcepaths: frameworks_base_subdirs, installable: false, @@ -1172,7 +1266,6 @@ droidstubs { name: "hiddenapi-mappings", defaults: ["metalava-api-stubs-default"], srcs: [ - ":framework-defaults-java-srcs", ":non_openjdk_java_files", ":openjdk_java_files", ":opt-telephony-common-srcs", @@ -1312,8 +1405,6 @@ filegroup { aidl_mapping { name: "framework-aidl-mappings", srcs: framework_srcs, - local_include_dirs: framework_aidl_local_include_dirs, - include_dirs: framework_aidl_external_include_dirs, output: "framework-aidl-mappings.txt", } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index 4c11947212f9..1bb9e967c025 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -18,13 +18,20 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; +import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.Settings; +import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; @@ -32,6 +39,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.job.ConstantsProto; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -55,6 +63,9 @@ public final class TimeController extends StateController { /** Delay alarm tag for logging purposes */ private final String DELAY_TAG = "*job.delay*"; + private final Handler mHandler; + private final TcConstants mTcConstants; + private long mNextJobExpiredElapsedMillis; private long mNextDelayExpiredElapsedMillis; @@ -70,6 +81,14 @@ public final class TimeController extends StateController { mNextJobExpiredElapsedMillis = Long.MAX_VALUE; mNextDelayExpiredElapsedMillis = Long.MAX_VALUE; mChainedAttributionEnabled = mService.isChainedAttributionEnabled(); + + mHandler = new Handler(mContext.getMainLooper()); + mTcConstants = new TcConstants(mHandler); + } + + @Override + public void onSystemServicesReady() { + mTcConstants.start(mContext.getContentResolver()); } /** @@ -294,8 +313,7 @@ public final class TimeController extends StateController { } else { if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) { if (DEBUG) { - Slog.i(TAG, - "Skipping " + job + " because delay won't make it ready."); + Slog.i(TAG, "Skipping " + job + " because delay won't make it ready."); } continue; } @@ -354,7 +372,8 @@ public final class TimeController extends StateController { /** * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's * delay will expire. - * This alarm <b>will</b> wake up the phone. + * This alarm <b>will not</b> wake up the phone if + * {@link TcConstants#USE_NON_WAKEUP_ALARM_FOR_DELAY} is true. */ private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); @@ -362,8 +381,11 @@ public final class TimeController extends StateController { return; } mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, - mNextDelayExpiredElapsedMillis, ws); + final int alarmType = + mTcConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY + ? AlarmManager.ELAPSED_REALTIME : AlarmManager.ELAPSED_REALTIME_WAKEUP; + updateAlarmWithListenerLocked(DELAY_TAG, alarmType, + mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis, ws); } /** @@ -377,16 +399,16 @@ public final class TimeController extends StateController { return; } mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, - mNextJobExpiredElapsedMillis, ws); + updateAlarmWithListenerLocked(DEADLINE_TAG, AlarmManager.ELAPSED_REALTIME_WAKEUP, + mDeadlineExpiredListener, mNextJobExpiredElapsedMillis, ws); } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis()); } - private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, - long alarmTimeElapsed, WorkSource ws) { + private void updateAlarmWithListenerLocked(String tag, @AlarmManager.AlarmType int alarmType, + OnAlarmListener listener, long alarmTimeElapsed, WorkSource ws) { ensureAlarmServiceLocked(); if (alarmTimeElapsed == Long.MAX_VALUE) { mAlarmService.cancel(listener); @@ -394,7 +416,7 @@ public final class TimeController extends StateController { if (DEBUG) { Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed); } - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed, + mAlarmService.set(alarmType, alarmTimeElapsed, AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws); } } @@ -422,9 +444,77 @@ public final class TimeController extends StateController { }; @VisibleForTesting - void recheckAlarmsLocked() { - checkExpiredDeadlinesAndResetAlarm(); - checkExpiredDelaysAndResetAlarm(); + class TcConstants extends ContentObserver { + private ContentResolver mResolver; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + private static final String KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY = + "use_non_wakeup_delay_alarm"; + + private static final boolean DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY = true; + + /** + * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't + * ready now. + */ + public boolean USE_NON_WAKEUP_ALARM_FOR_DELAY = DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY; + + /** + * Creates a content observer. + * + * @param handler The handler to run {@link #onChange} on, or null if none. + */ + TcConstants(Handler handler) { + super(handler); + } + + private void start(ContentResolver resolver) { + mResolver = resolver; + mResolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS), false, this); + onChange(true, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + final String constants = Settings.Global.getString( + mResolver, Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS); + + try { + mParser.setString(constants); + } catch (Exception e) { + // Failed to parse the settings string, log this and move on with defaults. + Slog.e(TAG, "Bad jobscheduler time controller settings", e); + } + + USE_NON_WAKEUP_ALARM_FOR_DELAY = mParser.getBoolean( + KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY); + // Intentionally not calling checkExpiredDelaysAndResetAlarm() here. There's no need to + // iterate through the entire list again for this constant change. The next delay alarm + // that is set will make use of the new constant value. + } + + private void dump(IndentingPrintWriter pw) { + pw.println(); + pw.println("TimeController:"); + pw.increaseIndent(); + pw.printPair(KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, + USE_NON_WAKEUP_ALARM_FOR_DELAY).println(); + pw.decreaseIndent(); + } + + private void dump(ProtoOutputStream proto) { + final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER); + proto.write(ConstantsProto.TimeController.USE_NON_WAKEUP_ALARM_FOR_DELAY, + USE_NON_WAKEUP_ALARM_FOR_DELAY); + proto.end(tcToken); + } + } + + @VisibleForTesting + @NonNull + TcConstants getTcConstants() { + return mTcConstants; } @Override @@ -501,4 +591,14 @@ public final class TimeController extends StateController { proto.end(mToken); proto.end(token); } + + @Override + public void dumpConstants(IndentingPrintWriter pw) { + mTcConstants.dump(pw); + } + + @Override + public void dumpConstants(ProtoOutputStream proto) { + mTcConstants.dump(proto); + } } diff --git a/api/current.txt b/api/current.txt index 593710314a0f..8a6d2bfae736 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4278,6 +4278,7 @@ package android.app { method @Deprecated public int checkOpNoThrow(@NonNull String, int, @NonNull String); method public void checkPackage(int, @NonNull String); method public void finishOp(@NonNull String, int, @NonNull String); + method public boolean isOpActive(@NonNull String, int, @NonNull String); method public int noteOp(@NonNull String, int, @NonNull String); method public int noteOpNoThrow(@NonNull String, int, @NonNull String); method public int noteProxyOp(@NonNull String, @NonNull String); @@ -4286,8 +4287,10 @@ package android.app { method public static String permissionToOp(String); method public int startOp(@NonNull String, int, @NonNull String); method public int startOpNoThrow(@NonNull String, int, @NonNull String); + method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener); method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener); method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener); + method public void stopWatchingActive(@NonNull android.app.AppOpsManager.OnOpActiveChangedListener); method public void stopWatchingMode(@NonNull android.app.AppOpsManager.OnOpChangedListener); method public int unsafeCheckOp(@NonNull String, int, @NonNull String); method public int unsafeCheckOpNoThrow(@NonNull String, int, @NonNull String); @@ -4335,6 +4338,10 @@ package android.app { field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1 } + public static interface AppOpsManager.OnOpActiveChangedListener { + method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean); + } + public static interface AppOpsManager.OnOpChangedListener { method public void onOpChanged(String, String); } @@ -9341,6 +9348,7 @@ package android.content { field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent"; field public static final String MIMETYPE_TEXT_PLAIN = "text/plain"; field public static final String MIMETYPE_TEXT_URILIST = "text/uri-list"; + field public static final String MIMETYPE_UNKNOWN = "application/octet-stream"; } public class ClipboardManager extends android.text.ClipboardManager { @@ -9688,6 +9696,7 @@ package android.content { method public Long getAsLong(String); method public Short getAsShort(String); method public String getAsString(String); + method public boolean isEmpty(); method public java.util.Set<java.lang.String> keySet(); method public void put(String, String); method public void put(String, Byte); @@ -13460,6 +13469,7 @@ package android.gesture { public final class GestureLibraries { method public static android.gesture.GestureLibrary fromFile(String); method public static android.gesture.GestureLibrary fromFile(java.io.File); + method @NonNull public static android.gesture.GestureLibrary fromFileDescriptor(@NonNull android.os.ParcelFileDescriptor); method public static android.gesture.GestureLibrary fromPrivateFile(android.content.Context, String); method public static android.gesture.GestureLibrary fromRawResource(android.content.Context, @RawRes int); } @@ -17073,6 +17083,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3 field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5 field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd + field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7 field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0 field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1 @@ -34521,6 +34532,7 @@ package android.os { method public static String getExternalStorageState(); method public static String getExternalStorageState(java.io.File); method @NonNull public static java.io.File getRootDirectory(); + method @NonNull public static java.io.File getStorageDirectory(); method @Deprecated public static String getStorageState(java.io.File); method public static boolean isExternalStorageEmulated(); method public static boolean isExternalStorageEmulated(@NonNull java.io.File); @@ -38344,8 +38356,10 @@ package android.provider { ctor public MediaStore(); method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context); + method public static boolean getIncludePending(@NonNull android.net.Uri); method public static android.net.Uri getMediaScannerUri(); method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri); + method public static boolean getRequireOriginal(@NonNull android.net.Uri); method @NonNull public static String getVersion(@NonNull android.content.Context); method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String); method @NonNull public static String getVolumeName(@NonNull android.net.Uri); @@ -38488,6 +38502,7 @@ package android.provider { public static final class MediaStore.Audio.Media implements android.provider.MediaStore.Audio.AudioColumns { ctor public MediaStore.Audio.Media(); method public static android.net.Uri getContentUri(String); + method @NonNull public static android.net.Uri getContentUri(@NonNull String, long); method @Deprecated @Nullable public static android.net.Uri getContentUriForPath(@NonNull String); field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; field public static final String DEFAULT_SORT_ORDER = "title_key"; @@ -38538,6 +38553,7 @@ package android.provider { public static final class MediaStore.Downloads implements android.provider.MediaStore.DownloadColumns { method @NonNull public static android.net.Uri getContentUri(@NonNull String); + method @NonNull public static android.net.Uri getContentUri(@NonNull String, long); field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download"; field @NonNull public static final android.net.Uri EXTERNAL_CONTENT_URI; field @NonNull public static final android.net.Uri INTERNAL_CONTENT_URI; @@ -38578,6 +38594,7 @@ package android.provider { ctor public MediaStore.Images.Media(); method @Deprecated public static android.graphics.Bitmap getBitmap(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException, java.io.IOException; method public static android.net.Uri getContentUri(String); + method @NonNull public static android.net.Uri getContentUri(@NonNull String, long); method @Deprecated public static String insertImage(android.content.ContentResolver, String, String, String) throws java.io.FileNotFoundException; method @Deprecated public static String insertImage(android.content.ContentResolver, android.graphics.Bitmap, String, String); method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]); @@ -38647,6 +38664,7 @@ package android.provider { public static final class MediaStore.Video.Media implements android.provider.MediaStore.Video.VideoColumns { ctor public MediaStore.Video.Media(); method public static android.net.Uri getContentUri(String); + method @NonNull public static android.net.Uri getContentUri(@NonNull String, long); field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; field public static final String DEFAULT_SORT_ORDER = "title"; field public static final android.net.Uri EXTERNAL_CONTENT_URI; @@ -42240,6 +42258,7 @@ package android.speech.tts { method public int speak(CharSequence, int, android.os.Bundle, String); method @Deprecated public int speak(String, int, java.util.HashMap<java.lang.String,java.lang.String>); method public int stop(); + method public int synthesizeToFile(@NonNull CharSequence, @NonNull android.os.Bundle, @NonNull android.os.ParcelFileDescriptor, @NonNull String); method public int synthesizeToFile(CharSequence, android.os.Bundle, java.io.File, String); method @Deprecated public int synthesizeToFile(String, java.util.HashMap<java.lang.String,java.lang.String>, String); field public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED"; diff --git a/api/removed.txt b/api/removed.txt index 74c1d3c7a1bf..db784a8d792c 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -481,9 +481,8 @@ package android.provider { @Deprecated public static class MediaStore.PendingParams { ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String); method public void setDownloadUri(@Nullable android.net.Uri); - method public void setPrimaryDirectory(@Nullable String); method public void setRefererUri(@Nullable android.net.Uri); - method public void setSecondaryDirectory(@Nullable String); + method public void setRelativePath(@Nullable String); } @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable { diff --git a/api/system-current.txt b/api/system-current.txt index 76d017c44822..86e5f2e31a43 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29,6 +29,7 @@ package android { field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"; field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE"; + field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE"; field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; field public static final String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"; @@ -192,6 +193,7 @@ package android { field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; + field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; @@ -351,6 +353,9 @@ package android.app { field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; + field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; + field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; + field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; @@ -366,6 +371,9 @@ package android.app { field public static final String OPSTR_WIFI_SCAN = "android:wifi_scan"; field public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard"; field public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms"; + field public static final String OPSTR_WRITE_MEDIA_AUDIO = "android:write_media_audio"; + field public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images"; + field public static final String OPSTR_WRITE_MEDIA_VIDEO = "android:write_media_video"; field public static final String OPSTR_WRITE_SMS = "android:write_sms"; field public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper"; field public static final int OP_FLAGS_ALL = 31; // 0x1f @@ -513,6 +521,8 @@ package android.app { } public class Notification implements android.os.Parcelable { + method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean); + method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions(); field public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; field public static final String CATEGORY_CAR_INFORMATION = "car_information"; field public static final String CATEGORY_CAR_WARNING = "car_warning"; @@ -521,6 +531,10 @@ package android.app { field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400 } + public static final class Notification.MessagingStyle.Message { + method @Nullable public static android.app.Notification.MessagingStyle.Message getMessageFromBundle(@NonNull android.os.Bundle); + } + public static final class Notification.TvExtender implements android.app.Notification.Extender { ctor public Notification.TvExtender(); ctor public Notification.TvExtender(android.app.Notification); @@ -543,6 +557,7 @@ package android.app { method public void populateFromXml(org.xmlpull.v1.XmlPullParser); method public org.json.JSONObject toJson() throws org.json.JSONException; method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException; + field public static final int USER_LOCKED_SOUND = 32; // 0x20 } public final class NotificationChannelGroup implements android.os.Parcelable { @@ -6648,6 +6663,10 @@ package android.service.notification { field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.SnoozeCriterion> CREATOR; } + public class StatusBarNotification implements android.os.Parcelable { + method public boolean isAppGroup(); + } + } package android.service.oemlock { @@ -6767,6 +6786,20 @@ package android.service.sms { } +package android.service.storage { + + public abstract class ExternalStorageService extends android.app.Service { + ctor public ExternalStorageService(); + method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onEndSession(@NonNull String) throws java.io.IOException; + method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull String, @NonNull String) throws java.io.IOException; + field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2 + field public static final int FLAG_SESSION_TYPE_FUSE = 1; // 0x1 + field public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService"; + } + +} + package android.service.textclassifier { public abstract class TextClassifierService extends android.app.Service { diff --git a/api/test-current.txt b/api/test-current.txt index e961511165cb..7e1c67de2c16 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -160,8 +160,6 @@ package android.app { method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int); - method public void startWatchingActive(@NonNull int[], @NonNull android.app.AppOpsManager.OnOpActiveChangedListener); - method public void stopWatchingActive(@NonNull android.app.AppOpsManager.OnOpActiveChangedListener); method public static int strOpToOp(@NonNull String); field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 @@ -193,6 +191,9 @@ package android.app { field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; + field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; + field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; + field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; @@ -208,6 +209,9 @@ package android.app { field public static final String OPSTR_WIFI_SCAN = "android:wifi_scan"; field public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard"; field public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms"; + field public static final String OPSTR_WRITE_MEDIA_AUDIO = "android:write_media_audio"; + field public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images"; + field public static final String OPSTR_WRITE_MEDIA_VIDEO = "android:write_media_video"; field public static final String OPSTR_WRITE_SMS = "android:write_sms"; field public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper"; field public static final int OP_COARSE_LOCATION = 0; // 0x0 @@ -293,10 +297,6 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR; } - public static interface AppOpsManager.OnOpActiveChangedListener { - method public void onOpActiveChanged(int, int, String, boolean); - } - public static final class AppOpsManager.OpEntry implements android.os.Parcelable { method public int describeContents(); method public long getDuration(); @@ -619,10 +619,13 @@ package android.content { method public int describeContents(); method public static android.content.AutofillOptions forWhitelistingItself(); method public boolean isAugmentedAutofillEnabled(@NonNull android.content.Context); + method public boolean isAutofillDisabledLocked(@NonNull android.content.ComponentName); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.AutofillOptions> CREATOR; + field public long appDisabledExpiration; field public boolean augmentedAutofillEnabled; field public final boolean compatModeEnabled; + field @Nullable public android.util.ArrayMap<java.lang.String,java.lang.Long> disabledActivities; field public final int loggingLevel; field @Nullable public android.util.ArraySet<android.content.ComponentName> whitelistedActivitiesForAugmentedAutofill; } @@ -661,6 +664,7 @@ package android.content { method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions); field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; + field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String ROLLBACK_SERVICE = "rollback"; field public static final String STATUS_BAR_SERVICE = "statusbar"; @@ -1733,7 +1737,6 @@ package android.os { public class Environment { method public static java.io.File buildPath(java.io.File, java.lang.String...); method @NonNull public static java.io.File getProductDirectory(); - method @NonNull public static java.io.File getStorageDirectory(); } public final class FileUtils { diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp index 0570c3a039ae..8dcb86537487 100644 --- a/cmds/incidentd/src/WorkDirectory.cpp +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -664,7 +664,7 @@ int64_t WorkDirectory::make_timestamp_ns_locked() { nanosleep(&spec, nullptr); } clock_gettime(CLOCK_REALTIME, &spec); - timestampNs = (spec.tv_sec) * 1000 + spec.tv_nsec; + timestampNs = int64_t(spec.tv_sec) * 1000 + spec.tv_nsec; } while (file_exists_locked(timestampNs)); return timestampNs; } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 6df0a8e14f6a..19fa64073183 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -129,17 +129,12 @@ cc_defaults { "libservices", "libprotoutil", "libstatslog", - "libhardware", - "libhardware_legacy", "libhidlbase", - "libhidltransport", - "libhwbinder", "android.frameworks.stats@1.0", "android.hardware.health@2.0", "android.hardware.power@1.0", "android.hardware.power@1.1", "android.hardware.power.stats@1.0", - "libpackagelistparser", "libsysutils", "libcutils", ], @@ -253,9 +248,6 @@ cc_test { "tests/e2e/GaugeMetric_e2e_push_test.cpp", "tests/e2e/GaugeMetric_e2e_pull_test.cpp", "tests/e2e/ValueMetric_pull_e2e_test.cpp", - "tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp", - "tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp", - "tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp", "tests/e2e/Anomaly_count_e2e_test.cpp", "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", "tests/e2e/ConfigTtl_e2e_test.cpp", diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index 60a4b236df11..52a1269798ca 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -110,20 +110,14 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf void CombinationConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const std::vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + vector<ConditionState>& conditionCache) const { // So far, this is fine as there is at most one child having sliced output. for (const int childIndex : mChildren) { if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { allConditions[childIndex]->isConditionMet(conditionParameters, allConditions, - dimensionFields, - isSubOutputDimensionFields, isPartialLink, - conditionCache, - dimensionsKeySet); + conditionCache); } } conditionCache[mIndex] = @@ -178,25 +172,6 @@ void CombinationConditionTracker::evaluateCondition( } } -ConditionState CombinationConditionTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const std::vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated); - // So far, this is fine as there is at most one child having sliced output. - for (const int childIndex : mChildren) { - conditionCache[childIndex] = conditionCache[childIndex] | - allConditions[childIndex]->getMetConditionDimension( - allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet); - } - evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); - if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) { - dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY); - } - return conditionCache[mIndex]; -} - bool CombinationConditionTracker::equalOutputDimensions( const std::vector<sp<ConditionTracker>>& allConditions, const vector<Matcher>& dimensions) const { diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index 481cb200d8e6..e3d860127780 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -43,17 +43,8 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + std::vector<ConditionState>& conditionCache) const override; // Only one child predicate can have dimension. const std::set<HashableDimensionKey>* getChangedToTrueDimensions( diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 1f4266b61cdf..e94ea6586f05 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -84,29 +84,14 @@ public: // condition. // [allConditions]: all condition trackers. This is needed because the condition evaluation is // done recursively - // [dimensionFields]: the needed dimension fields which should be all or subset of the condition - // tracker output dimension. - // [isSubOutputDimensionFields]: true if the needed dimension fields which is strictly subset of - // the condition tracker output dimension. // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields // in the condition tracker output dimension. // [conditionCache]: the cache holding the condition evaluation values. - // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination - // condition, it assumes that only one child predicate is sliced. virtual void isConditionMet( const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0; - - virtual ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0; + std::vector<ConditionState>& conditionCache) const = 0; // return the list of LogMatchingTracker index that this ConditionTracker uses. virtual const std::set<int>& getLogTrackerIndex() const { diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index 23a9d371145e..4f44a69ba980 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -25,27 +25,15 @@ using std::string; using std::vector; ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet) { + const bool isPartialLink) { vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated); mAllConditions[index]->isConditionMet( - parameters, mAllConditions, dimensionFields, isSubOutputDimensionFields, isPartialLink, - cache, *dimensionKeySet); + parameters, mAllConditions, isPartialLink, + cache); return cache[index]; } -ConditionState ConditionWizard::getMetConditionDimension( - const int index, const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const { - return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields, - isSubOutputDimensionFields, - *dimensionsKeySet); -} - const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions( const int index) const { return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions); @@ -82,4 +70,4 @@ bool ConditionWizard::equalOutputDimensions(const int index, const vector<Matche } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 2c8814772839..892647910d9f 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -40,15 +40,7 @@ public: // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case, // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet); - - virtual ConditionState getMetConditionDimension( - const int index, const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const; + const bool isPartialLink); virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const; virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 87104a34c009..0c92149f4c96 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -344,11 +344,8 @@ void SimpleConditionTracker::evaluateCondition( void SimpleConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + vector<ConditionState>& conditionCache) const { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. @@ -360,18 +357,13 @@ void SimpleConditionTracker::isConditionMet( if (pair == conditionParameters.end()) { ConditionState conditionState = ConditionState::kNotEvaluated; - if (dimensionFields.size() > 0 && dimensionFields[0].mMatcher.getTag() == mDimensionTag) { - conditionState = conditionState | getMetConditionDimension( - allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet); - } else { - conditionState = conditionState | mInitialValue; - if (!mSliced) { - const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); - if (itr != mSlicedConditionState.end()) { - ConditionState sliceState = - itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - } + conditionState = conditionState | mInitialValue; + if (!mSliced) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; } } conditionCache[mIndex] = conditionState; @@ -389,15 +381,6 @@ void SimpleConditionTracker::isConditionMet( slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; if (slice.first.contains(key)) { conditionState = conditionState | sliceState; - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, slice.first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(slice.first); - } - } } } } else { @@ -407,15 +390,6 @@ void SimpleConditionTracker::isConditionMet( ConditionState sliceState = startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; conditionState = conditionState | sliceState; - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(startedCountIt->first); - } - } } } @@ -423,41 +397,6 @@ void SimpleConditionTracker::isConditionMet( VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } -ConditionState SimpleConditionTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - ConditionState conditionState = mInitialValue; - if (dimensionFields.size() == 0 || mOutputDimensions.size() == 0 || - dimensionFields[0].mMatcher.getTag() != mOutputDimensions[0].mMatcher.getTag()) { - const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); - if (itr != mSlicedConditionState.end()) { - ConditionState sliceState = - itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - } - return conditionState; - } - - for (const auto& slice : mSlicedConditionState) { - ConditionState sliceState = - slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, slice.first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(slice.first); - } - } - } - return conditionState; -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 47d1eceb9022..5c5cc565f783 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -48,17 +48,8 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + std::vector<ConditionState>& conditionCache) const override; virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( const std::vector<sp<ConditionTracker>>& allConditions) const { diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp index 1965ce6015ff..18c7178dd7d7 100644 --- a/cmds/statsd/src/condition/StateTracker.cpp +++ b/cmds/statsd/src/condition/StateTracker.cpp @@ -178,11 +178,8 @@ void StateTracker::evaluateCondition(const LogEvent& event, void StateTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + vector<ConditionState>& conditionCache) const { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); @@ -193,10 +190,6 @@ void StateTracker::isConditionMet( if (pair == conditionParameters.end()) { if (mSlicedState.size() > 0) { conditionCache[mIndex] = ConditionState::kTrue; - - for (const auto& state : mSlicedState) { - dimensionsKeySet.insert(state.second); - } } else { conditionCache[mIndex] = ConditionState::kUnknown; } @@ -208,25 +201,9 @@ void StateTracker::isConditionMet( auto it = mSlicedState.find(primaryKey); if (it != mSlicedState.end()) { conditionCache[mIndex] = ConditionState::kTrue; - dimensionsKeySet.insert(it->second); - } -} - -ConditionState StateTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - if (mSlicedState.size() > 0) { - for (const auto& state : mSlicedState) { - dimensionsKeySet.insert(state.second); - } - return ConditionState::kTrue; } - - return mInitialValue; } } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h index 2bdf98c34c32..5ae4441713cd 100644 --- a/cmds/statsd/src/condition/StateTracker.h +++ b/cmds/statsd/src/condition/StateTracker.h @@ -55,22 +55,8 @@ public: */ void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - /** - * Note: dimensionFields will be ignored in StateTracker, because we demand metrics - * must take the entire dimension fields from StateTracker. This is to make implementation - * simple and efficient. - */ - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + std::vector<ConditionState>& conditionCache) const override; virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( const std::vector<sp<ConditionTracker>>& allConditions) const { @@ -128,4 +114,4 @@ private: } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index c023e6f77e7c..23d025f82c26 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -48,7 +48,6 @@ const int FIELD_ID_COUNT_METRICS = 5; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for CountMetricDataWrapper @@ -82,12 +81,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); - - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -100,8 +94,6 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - flushIfNeededLocked(startTimeNs); // Adjust start for partial bucket mCurrentBucketStartTimeNs = startTimeNs; @@ -171,13 +163,6 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } - } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 96fbf7fb5ebe..cca793b2146a 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -47,7 +47,6 @@ const int FIELD_ID_DURATION_METRICS = 6; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for DurationMetricDataWrapper const int FIELD_ID_DATA = 1; @@ -100,12 +99,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat ALOGE("Position ANY in dimension_in_what not supported."); } - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } - - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -115,19 +109,16 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); mUnSlicedPartCondition = ConditionState::kUnknown; mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); - if (mWizard != nullptr && mConditionTrackerIndex >= 0) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(mConditionTrackerIndex, mDimensionsInCondition); - if (mMetric2ConditionLinks.size() == 1) { - mHasLinksToAllConditionDimensionsInTracker = - mWizard->equalOutputDimensions(mConditionTrackerIndex, - mMetric2ConditionLinks.begin()->conditionFields); - } + if (mWizard != nullptr && mConditionTrackerIndex >= 0 && + mMetric2ConditionLinks.size() == 1) { + mHasLinksToAllConditionDimensionsInTracker = + mWizard->equalOutputDimensions(mConditionTrackerIndex, + mMetric2ConditionLinks.begin()->conditionFields); } flushIfNeededLocked(startTimeNs); // Adjust start for partial bucket @@ -164,13 +155,13 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, + mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, + mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); } @@ -178,13 +169,11 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( // SlicedConditionChange optimization case 1: // 1. If combination condition, logical operation is AND, only one sliced child predicate. -// 2. No condition in dimension -// 3. The links covers all dimension fields in the sliced child condition predicate. +// 2. The links covers all dimension fields in the sliced child condition predicate. void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition, const int64_t eventTime) { if (mMetric2ConditionLinks.size() != 1 || - !mHasLinksToAllConditionDimensionsInTracker || - !mDimensionsInCondition.empty()) { + !mHasLinksToAllConditionDimensionsInTracker) { return; } @@ -249,178 +238,20 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio } } - -// SlicedConditionChange optimization case 2: -// 1. If combination condition, logical operation is AND, only one sliced child predicate. -// 2. Has dimensions_in_condition and it equals to the output dimensions of the sliced predicate. -void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(bool condition, - const int64_t eventTime) { - if (mMetric2ConditionLinks.size() > 1 || !mSameConditionDimensionsInTracker) { - return; - } - - auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex); - auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex); - - bool currentUnSlicedPartCondition = true; - if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) { - ConditionState unslicedPartState = - mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex); - // When the unsliced part is still false, return directly. - if (mUnSlicedPartCondition == ConditionState::kFalse && - unslicedPartState == ConditionState::kFalse) { - return; - } - mUnSlicedPartCondition = unslicedPartState; - currentUnSlicedPartCondition = mUnSlicedPartCondition > 0; - } - - const std::set<HashableDimensionKey>* trueDimensionsToProcess = nullptr; - const std::set<HashableDimensionKey>* falseDimensionsToProcess = nullptr; - - std::set<HashableDimensionKey> currentTrueConditionDimensions; - if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr || - (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) { - mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, ¤tTrueConditionDimensions); - trueDimensionsToProcess = ¤tTrueConditionDimensions; - } else if (currentUnSlicedPartCondition) { - // Handles the condition change from the sliced predicate. If the unsliced condition state - // is not true, not need to do anything. - trueDimensionsToProcess = dimensionsChangedToTrue; - falseDimensionsToProcess = dimensionsChangedToFalse; - } - - if (trueDimensionsToProcess == nullptr && falseDimensionsToProcess == nullptr) { - return; - } - - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - if (falseDimensionsToProcess != nullptr) { - for (const auto& changedDim : *falseDimensionsToProcess) { - auto condIt = whatIt.second.find(changedDim); - if (condIt != whatIt.second.end()) { - condIt->second->onConditionChanged(false, eventTime); - } - } - } - if (trueDimensionsToProcess != nullptr) { - HashableDimensionKey linkedConditionDimensionKey; - if (!trueDimensionsToProcess->empty() && mMetric2ConditionLinks.size() == 1) { - getDimensionForCondition(whatIt.first.getValues(), - mMetric2ConditionLinks[0], - &linkedConditionDimensionKey); - } - for (auto& trueDim : *trueDimensionsToProcess) { - auto condIt = whatIt.second.find(trueDim); - if (condIt != whatIt.second.end()) { - condIt->second->onConditionChanged( - currentUnSlicedPartCondition, eventTime); - } else { - if (mMetric2ConditionLinks.size() == 0 || - trueDim.contains(linkedConditionDimensionKey)) { - if (!whatIt.second.empty()) { - auto newEventKey = MetricDimensionKey(whatIt.first, trueDim); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - unique_ptr<DurationTracker> newTracker = - whatIt.second.begin()->second->clone(eventTime); - if (newTracker != nullptr) { - newTracker->setEventKey(newEventKey); - newTracker->onConditionChanged(true, eventTime); - whatIt.second[trueDim] = std::move(newTracker); - } - } - } - } - } - } - } -} - void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition, const int64_t eventTimeNs) { bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex); - if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker && - mDimensionsInCondition.empty()) { + if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) { onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs); return; } - if (changeDimTrackable && mSameConditionDimensionsInTracker && - mMetric2ConditionLinks.size() <= 1) { - onSlicedConditionMayChangeLocked_opt2(overallCondition, eventTimeNs); - return; - } - // Now for each of the on-going event, check if the condition has changed for them. for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { for (auto& pair : whatIt.second) { pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); } } - - if (mDimensionsInCondition.empty()) { - return; - } - - if (mMetric2ConditionLinks.empty()) { - std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet; - mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - &conditionDimensionsKeySet); - for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (const auto& pair : whatIt.second) { - conditionDimensionsKeySet.erase(pair.first); - } - } - for (const auto& conditionDimension : conditionDimensionsKeySet) { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - if (!whatIt.second.empty()) { - auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - unique_ptr<DurationTracker> newTracker = - whatIt.second.begin()->second->clone(eventTimeNs); - if (newTracker != nullptr) { - newTracker->setEventKey(MetricDimensionKey(newEventKey)); - newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs); - whatIt.second[conditionDimension] = std::move(newTracker); - } - } - } - } - } else { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - ConditionKey conditionKey; - for (const auto& link : mMetric2ConditionLinks) { - getDimensionForCondition(whatIt.first.getValues(), link, - &conditionKey[link.conditionId]); - } - std::unordered_set<HashableDimensionKey> conditionDimensionsKeys; - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionsKeys); - - for (const auto& conditionDimension : conditionDimensionsKeys) { - if (!whatIt.second.empty() && - whatIt.second.find(conditionDimension) == whatIt.second.end()) { - auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - auto newTracker = whatIt.second.begin()->second->clone(eventTimeNs); - if (newTracker != nullptr) { - newTracker->setEventKey(newEventKey); - newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs); - whatIt.second[conditionDimension] = std::move(newTracker); - } - } - } - } - } } void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, @@ -526,12 +357,6 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); @@ -790,52 +615,24 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, bool condition; ConditionKey conditionKey; - std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mMetric2ConditionLinks) { getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); } auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &dimensionKeysInCondition); + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); condition = conditionState == ConditionState::kTrue; - if (mDimensionsInCondition.empty() && condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); - } } else { // TODO: The unknown condition state is not handled here, we should fix it. condition = mCondition == ConditionState::kTrue; - if (condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); - } } condition = condition && mIsActive; - if (dimensionKeysInCondition.empty()) { - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), - conditionKey, condition, event); - } else { - auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); - // If the what dimension is already there, we should update all the trackers even - // the condition is false. - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& condIt : whatIt->second) { - const bool cond = dimensionKeysInCondition.find(condIt.first) != - dimensionKeysInCondition.end() && condition; - handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first), - conditionKey, cond, event); - dimensionKeysInCondition.erase(condIt.first); - } - } - for (const auto& conditionDimension : dimensionKeysInCondition) { - handleStartEvent(MetricDimensionKey(dimensionInWhat, conditionDimension), conditionKey, - condition, event); - } - } + handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), + conditionKey, condition, event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index a64bbc1056e0..1f423cd384ef 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -48,7 +48,6 @@ const int FIELD_ID_GAUGE_METRICS = 8; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for GaugeMetricDataWrapper const int FIELD_ID_DATA = 1; @@ -115,10 +114,6 @@ GaugeMetricProducer::GaugeMetricProducer( mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } - if (metric.links().size() > 0) { for (const auto& link : metric.links()) { Metric2Condition mc; @@ -127,10 +122,9 @@ GaugeMetricProducer::GaugeMetricProducer( translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); flushIfNeededLocked(startTimeNs); // Kicks off the puller immediately. @@ -209,12 +203,6 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 92752b29ecda..1ab4fdf6e90c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -52,38 +52,24 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo bool condition; ConditionKey conditionKey; - std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mMetric2ConditionLinks) { getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); } auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &dimensionKeysInCondition); + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); condition = (conditionState == ConditionState::kTrue); } else { // TODO: The unknown condition state is not handled here, we should fix it. condition = mCondition == ConditionState::kTrue; } - if (mDimensionsInCondition.empty() && condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); - } - HashableDimensionKey dimensionInWhat; filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY); - for (const auto& conditionDimensionKey : dimensionKeysInCondition) { - metricKey.setDimensionKeyInCondition(conditionDimensionKey); - onMatchedLogEventInternalLocked( - matcherIndex, metricKey, conditionKey, condition, event); - } - if (dimensionKeysInCondition.empty()) { - onMatchedLogEventInternalLocked( - matcherIndex, metricKey, conditionKey, condition, event); - } + onMatchedLogEventInternalLocked( + matcherIndex, metricKey, conditionKey, condition, event); } bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 09ad2903fa4c..94f833b20814 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -88,7 +88,6 @@ public: mConditionTrackerIndex(conditionIndex), mContainANYPositionInDimensionsInWhat(false), mSliceByPositionALL(false), - mSameConditionDimensionsInTracker(false), mHasLinksToAllConditionDimensionsInTracker(false), mIsActive(true) { } @@ -349,15 +348,10 @@ protected: int mConditionTrackerIndex; vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config - vector<Matcher> mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config bool mContainANYPositionInDimensionsInWhat; bool mSliceByPositionALL; - // True iff the condition dimensions equal to the sliced dimensions in the simple condition - // tracker. This field is always false for combinational condition trackers. - bool mSameConditionDimensionsInTracker; - // True iff the metric to condition links cover all dimension fields in the condition tracker. // This field is always false for combinational condition trackers. bool mHasLinksToAllConditionDimensionsInTracker; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 8efca1e10de5..3dad61465416 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -260,17 +260,6 @@ private: FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition); - - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 0e33a0f9f29b..bc1602497c39 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -51,7 +51,6 @@ const int FIELD_ID_VALUE_METRICS = 7; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for ValueMetricDataWrapper const int FIELD_ID_DATA = 1; @@ -127,10 +126,6 @@ ValueMetricProducer::ValueMetricProducer( mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } - if (metric.links().size() > 0) { for (const auto& link : metric.links()) { Metric2Condition mc; @@ -139,11 +134,10 @@ ValueMetricProducer::ValueMetricProducer( translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs); mCurrentBucketNum += numBucketsForward; @@ -243,12 +237,6 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 081e61ed21fa..6b5c2994a0c8 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -60,7 +60,7 @@ class DurationTracker { public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, - const std::vector<Matcher>& dimensionInCondition, bool nesting, + bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) @@ -70,7 +70,6 @@ public: mWizard(wizard), mConditionTrackerIndex(conditionIndex), mBucketSizeNs(bucketSizeNs), - mDimensionInCondition(dimensionInCondition), mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), @@ -180,8 +179,6 @@ protected: const int64_t mBucketSizeNs; - const std::vector<Matcher>& mDimensionInCondition; - const bool mNested; int64_t mCurrentBucketStartTimeNs; @@ -196,7 +193,6 @@ protected: const bool mConditionSliced; - bool mSameConditionDimensionsInTracker; bool mHasLinksToAllConditionDimensionsInTracker; std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 6868b8c24c71..df66cb0c53e5 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -27,18 +27,14 @@ namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, - const vector<Matcher>& dimensionInCondition, bool nesting, + bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, anomalyTrackers) { - if (mWizard != nullptr) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition); - } } unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) { @@ -252,17 +248,11 @@ void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, if (pair.second.state == kStopped) { continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query( - mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - bool conditionMet = - (conditionState == ConditionState::kTrue) && - (mDimensionInCondition.size() == 0 || - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != - conditionDimensionKeySet.end()); + mConditionTrackerIndex, pair.second.conditionKeys, + !mHasLinksToAllConditionDimensionsInTracker); + bool conditionMet = (conditionState == ConditionState::kTrue); + VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet); noteConditionChanged(pair.first, conditionMet, timestamp); } diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 8e8f2cd6c582..d0371da096ea 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -30,7 +30,7 @@ class MaxDurationTracker : public DurationTracker { public: MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, - const std::vector<Matcher>& dimensionInCondition, bool nesting, + bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 956383a99eea..b0fd975b2328 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,20 +26,16 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, const vector<Matcher>& dimensionInCondition, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; - if (mWizard != nullptr) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition); - } } unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) { @@ -227,17 +223,10 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, ++it; continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query(mConditionTrackerIndex, condIt->second, - mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - if (conditionState != ConditionState::kTrue || - (mDimensionInCondition.size() != 0 && - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) == - conditionDimensionKeySet.end())) { + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState != ConditionState::kTrue) { startedToPaused.push_back(*it); it = mStarted.erase(it); VLOG("Key %s started -> paused", key.toString().c_str()); @@ -262,17 +251,10 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, ++it; continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], - mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - if (conditionState == ConditionState::kTrue && - (mDimensionInCondition.size() == 0 || - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != - conditionDimensionKeySet.end())) { + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState == ConditionState::kTrue) { pausedToStarted.push_back(*it); it = mPaused.erase(it); VLOG("Key %s paused -> started", key.toString().c_str()); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 8e73256d4a01..43c48d5f536b 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -29,7 +29,7 @@ class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, const std::vector<Matcher>& dimensionInCondition, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index b87547056ad3..d9c04f248af0 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -66,13 +66,13 @@ message CountBucketInfo { message CountMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; repeated CountBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message DurationBucketInfo { @@ -92,13 +92,13 @@ message DurationBucketInfo { message DurationMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; repeated DurationBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message ValueBucketInfo { @@ -136,13 +136,13 @@ message ValueBucketInfo { message ValueMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; repeated ValueBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message GaugeBucketInfo { @@ -166,13 +166,13 @@ message GaugeBucketInfo { message GaugeMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; repeated GaugeBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message StatsLogReport { @@ -220,7 +220,7 @@ message StatsLogReport { optional DimensionsValue dimensions_path_in_what = 11; - optional DimensionsValue dimensions_path_in_condition = 12; + optional DimensionsValue dimensions_path_in_condition = 12 [deprecated = true]; // DO NOT USE field 13. @@ -363,7 +363,7 @@ message StatsdStatsReport { repeated ConditionStats condition_stats = 14; repeated MetricStats metric_stats = 15; repeated AlertStats alert_stats = 16; - repeated MetricStats metric_dimension_in_condition_stats = 17; + repeated MetricStats metric_dimension_in_condition_stats = 17 [deprecated = true]; message Annotation { optional int64 field_int64 = 1; optional int32 field_int32 = 2; @@ -483,7 +483,7 @@ message AlertTriggerDetails { message MetricValue { optional int64 metric_id = 1; optional DimensionsValue dimension_in_what = 2; - optional DimensionsValue dimension_in_condition = 3; + optional DimensionsValue dimension_in_condition = 3 [deprecated = true]; optional int64 value = 4; } oneof value { diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 79c06b98a82d..1b7f39806608 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -182,7 +182,7 @@ message CountMetric { optional FieldMatcher dimensions_in_what = 4; - optional FieldMatcher dimensions_in_condition = 7; + optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; optional TimeUnit bucket = 5; @@ -207,7 +207,7 @@ message DurationMetric { optional FieldMatcher dimensions_in_what = 6; - optional FieldMatcher dimensions_in_condition = 8; + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; optional TimeUnit bucket = 7; } @@ -225,7 +225,7 @@ message GaugeMetric { optional FieldMatcher dimensions_in_what = 5; - optional FieldMatcher dimensions_in_condition = 8; + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; optional TimeUnit bucket = 6; @@ -259,7 +259,7 @@ message ValueMetric { optional FieldMatcher dimensions_in_what = 5; - optional FieldMatcher dimensions_in_condition = 9; + optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; optional TimeUnit bucket = 6; diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index e826a52c2f33..6eaa2311e5ed 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -268,8 +268,6 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { std::vector<sp<ConditionTracker>> allConditions; for (Position position : { Position::FIRST, Position::LAST}) { - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, @@ -321,9 +319,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -389,9 +387,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } @@ -399,8 +397,6 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { std::vector<sp<ConditionTracker>> allConditions; - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, @@ -445,9 +441,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - true, true, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + true, + conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -489,10 +485,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - dimensionKeys.clear(); - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - true, true, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + true, + conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } @@ -500,8 +495,6 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { std::vector<sp<ConditionTracker>> allConditions; for (Position position : { Position::FIRST, Position::LAST }) { - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -555,9 +548,9 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by uid2 @@ -594,9 +587,9 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); @@ -628,17 +621,17 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); // TEST QUERY const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, + false, + conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } } diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp deleted file mode 100644 index e4186b7200a0..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp +++ /dev/null @@ -1,961 +0,0 @@ -// 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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition, - bool hashStringInReport) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensions->add_child()->set_field(2); // job name field. - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - config.set_hash_strings_in_metric_report(hashStringInReport); - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - auto dimensionWhat = metric->mutable_dimensions_in_what(); - dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensionWhat->add_child()->set_field(2); // job name field. - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -/* - The following test has the following input. - -{ 10000000002 10000000002 (8)9999[I], [S], job0[S], 1[I], } -{ 10000000010 10000000010 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000011 10000000011 (29)1[I], } -{ 10000000040 10000000040 (29)2[I], } -{ 10000000050 10000000050 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 10000000101 10000000101 (8)9999[I], [S], job0[S], 0[I], } -{ 10000000102 10000000102 (29)1[I], } -{ 10000000200 10000000200 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000201 10000000201 (8)9999[I], [S], job2[S], 1[I], } -{ 10000000400 10000000400 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 1[I], } -{ 10000000401 10000000401 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000450 10000000450 (29)2[I], } -{ 10000000500 10000000500 (8)9999[I], [S], job2[S], 0[I], } -{ 10000000600 10000000600 (8)8888[I], [S], job2[S], 1[I], } -{ 10000000650 10000000650 (29)1[I], } -{ 309999999999 309999999999 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 0[I], } -{ 310000000100 310000000100 (29)2[I], } -{ 310000000300 310000000300 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 310000000600 310000000600 (8)8888[I], [S], job1[S], 1[I], } -{ 310000000640 310000000640 (29)1[I], } -{ 310000000650 310000000650 (29)2[I], } -{ 310000000700 310000000700 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 310000000850 310000000850 (8)8888[I], [S], job2[S], 0[I], } -{ 310000000900 310000000900 (8)8888[I], [S], job1[S], 0[I], } -*/ -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) { - for (const bool hashStringInReport : { true, false }) { - for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) { - for (auto aggregationType : {DurationMetric::MAX_SPARSE, DurationMetric::SUM}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension, - hashStringInReport); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis( - config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 11)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 40)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 102)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 650)); - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 640)); - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 650)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", - bucketStartTimeNs + bucketSizeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 10)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 650); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 650); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100 + 650 - 640); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 110); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition) { - for (bool isFullLink : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition( - aggregationType, !isFullLink); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 55)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 120)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 121)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 501)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_PartialLink_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool hashStringInReport) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - config.set_hash_strings_in_metric_report(hashStringInReport); - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *metric->mutable_dimensions_in_condition() = *syncDimension; - - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition) { - for (const bool hashStringInReport : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = - CreateDurationMetricConfig_PartialLink_AND_CombinationCondition( - aggregationType, hashStringInReport); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 55)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 120)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 121)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 501)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 600 + 50); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 50); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp deleted file mode 100644 index f3ecd56dd946..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp +++ /dev/null @@ -1,816 +0,0 @@ -// 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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = screenIsOffPredicate; - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by any attribution node and both by uid and tag. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidAndTagDimensions(android::util::WAKELOCK_STATE_CHANGED, - {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate); - - auto metric = config.add_count_metric(); - metric->set_id(StringToId("ScreenBrightnessChangeMetric")); - metric->set_what(screenBrightnessChangeAtomMatcher.id()); - metric->set_condition(combinationPredicate->id()); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - metric->set_bucket(FIVE_MINUTES); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition) { - ConfigKey cfgKey; - auto config = CreateCountMetric_NoLink_CombinationCondition_Config(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - events.push_back( - CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 1)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs - 10)); - - events.push_back(CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 200)); - events.push_back( - CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1)); - - events.push_back(CreateAcquireWakelockEvent(attributions2, "wl2", - bucketStartTimeNs + bucketSizeNs - 100)); - events.push_back(CreateReleaseWakelockEvent(attributions2, "wl2", - bucketStartTimeNs + 2 * bucketSizeNs - 50)); - - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 11)); - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 101)); - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 201)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + 203)); - events.push_back( - CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 99)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back(CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs + 2)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 11)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 9)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 1)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - - EXPECT_EQ(countMetrics.data_size(), 7); - auto data = countMetrics.data(0); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - - data = countMetrics.data(1); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(2); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 3); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(3); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 333); - - data = countMetrics.data(4); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - - data = countMetrics.data(5); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(6); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 333); -} - -namespace { - -StatsdConfig CreateCountMetric_Link_CombinationCondition() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto appCrashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = appCrashMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_count_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("AppCrashMetric")); - metric->set_what(appCrashMatcher.id()); - metric->set_condition(combinationPredicate->id()); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - - // Links between crash atom and condition of app is in syncing. - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition) { - ConfigKey cfgKey; - auto config = CreateCountMetric_Link_CombinationCondition(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11)); - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101)); - events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101)); - - events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201)); - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211)); - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401)); - events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401)); - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301)); - - events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701)); - - events.push_back( - CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 700)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions2, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 600)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - - EXPECT_EQ(countMetrics.data_size(), 5); - auto data = countMetrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - - data = countMetrics.data(4); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto inBatterySaverModePredicate = CreateBatterySaverModePredicate(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field */); - - *config.add_predicate() = inBatterySaverModePredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("BatterySaverModeDurationMetric")); - metric->set_what(inBatterySaverModePredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) { - for (auto aggregationType : { DurationMetric::MAX_SPARSE, DurationMetric::SUM}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1)); - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 800)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); - - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - - data = metrics.data(1); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); - } else { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 300); - } - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - } else { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs + 700 - 600); - } - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_Link_CombinationCondition( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field */); - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */}); - - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - *config.add_predicate() = isInBackgroundPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("AppInBackgroundMetric")); - metric->set_what(isInBackgroundPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - - // Links between crash atom and condition of app is in syncing. - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_Link_CombinationCondition(aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101)); - events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110)); - - events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201)); - events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399)); - events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 801)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); - - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 100 - 201); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 299); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp deleted file mode 100644 index 489bb0b21a2e..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp +++ /dev/null @@ -1,814 +0,0 @@ -// 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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_SimpleCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - auto dimensionWhat = metric->mutable_dimensions_in_what(); - dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensionWhat->add_child()->set_field(2); // job name field. - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition) { - for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_SimpleCondition( - aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 10)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 300); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 ); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig createDurationMetric_Link_SimpleConditionConfig( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition) { - for (bool isFullLink : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = createDurationMetric_Link_SimpleConditionConfig( - aggregationType, !isFullLink); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig createDurationMetric_PartialLink_SimpleConditionConfig( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *metric->mutable_dimensions_in_condition() = *syncDimension; - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = createDurationMetric_PartialLink_SimpleConditionConfig( - aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(4, metrics.data_size()); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 400 - 100); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 67c704eb87fd..839daa4b7f8a 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -187,9 +187,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, bucketStartTimeNs, bucketStartTimeNs); diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index d2fd95c818cf..f30873b92afc 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -113,9 +113,9 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index b9553a8fded8..47c21aa8f1df 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -485,10 +485,6 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { dim->set_field(tagId); dim->add_child()->set_field(1); - dim = metric.mutable_dimensions_in_condition(); - dim->set_field(conditionTag); - dim->add_child()->set_field(1); - UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); @@ -496,18 +492,14 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, _, _, _, _, _)) + EXPECT_CALL(*wizard, query(_, _, _)) .WillRepeatedly( Invoke([](const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, const bool isSubsetDim, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet) { - dimensionKeySet->clear(); + const bool isPartialLink) { int pos[] = {1, 0, 0}; Field f(conditionTag, pos, 0); HashableDimensionKey key; key.mutableValues()->emplace_back(f, Value((int32_t)1000000)); - dimensionKeySet->insert(key); return ConditionState::kTrue; })); @@ -538,9 +530,6 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { EXPECT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1000, key.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(1UL, key.getDimensionKeyInCondition().getValues().size()); - EXPECT_EQ(1000000, key.getDimensionKeyInCondition().getValues()[0].mValue.int_value); - EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index bf047520cc77..100220b730d7 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -52,7 +52,6 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -63,7 +62,7 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -88,7 +87,6 @@ TEST(MaxDurationTrackerTest, TestStopAll) { const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -99,7 +97,7 @@ TEST(MaxDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -124,7 +122,6 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -135,7 +132,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -165,7 +162,6 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -176,7 +172,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -202,7 +198,6 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { const HashableDimensionKey conditionDimKey = key1; - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -223,7 +218,7 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); @@ -246,7 +241,6 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -273,7 +267,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -295,7 +289,6 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { // This tests that we correctly compute the predicted time of an anomaly assuming that the current // state continues forward as-is. TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -333,7 +326,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -381,7 +374,6 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { // Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the // elapsed duration of B. TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -416,7 +408,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -434,4 +426,4 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif
\ No newline at end of file +#endif diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 7c2b4236add8..1cd7bdbf7bb0 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -51,7 +51,6 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -62,7 +61,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -84,7 +83,6 @@ TEST(OringDurationTrackerTest, TestDurationNested) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -94,7 +92,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -117,7 +115,6 @@ TEST(OringDurationTrackerTest, TestStopAll) { {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -127,7 +124,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -147,7 +144,6 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -158,7 +154,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); @@ -186,13 +182,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -203,7 +198,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); @@ -224,13 +219,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) + EXPECT_CALL(*wizard, query(_, key1, _)) .Times(2) .WillOnce(Return(ConditionState::kFalse)) .WillOnce(Return(ConditionState::kTrue)); @@ -243,7 +237,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); @@ -266,13 +260,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -282,7 +275,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); @@ -306,7 +299,6 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -324,7 +316,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -371,7 +363,6 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { } TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -387,7 +378,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1, - dimensionInCondition, + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -415,7 +406,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { for (int j = 0; j < 3; j++) { int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC; for (int i = 0; i <= 7; ++i) { - vector<Matcher> dimensionInCondition; + Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -432,7 +423,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, - wizard, 1, dimensionInCondition, + wizard, 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -472,7 +463,6 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -491,7 +481,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {anomalyTracker}); @@ -522,7 +512,6 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -541,7 +530,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, false, {anomalyTracker}); @@ -589,4 +578,4 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif
\ No newline at end of file +#endif diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 97c107228f9c..329e39fc4eff 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -26,11 +26,9 @@ namespace statsd { class MockConditionWizard : public ConditionWizard { public: - MOCK_METHOD6(query, + MOCK_METHOD3(query, ConditionState(const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, - const bool isSubsetDim, const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet)); + const bool isPartialLink)); }; class MockStatsPullerManager : public StatsPullerManager { @@ -55,4 +53,4 @@ void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/config/preloaded-classes b/config/preloaded-classes index ea50999e9d56..778a4d7bcda7 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -200,6 +200,7 @@ android.app.ContentProviderHolder android.app.ContextImpl$1 android.app.ContextImpl$ApplicationContentResolver android.app.ContextImpl +android.app.DeviceIdleFrameworkInitializer android.app.DexLoadReporter android.app.Dialog$ListenersHandler android.app.Dialog diff --git a/config/preloaded-classes-extra b/config/preloaded-classes-extra index 94849fb97433..4bfa873f63f2 100644 --- a/config/preloaded-classes-extra +++ b/config/preloaded-classes-extra @@ -1,5 +1,6 @@ # JobSchedulerFrameworkInitializer must always be preloaded because it registers the job scheduler # service wrapper to SystemServiceRegistry. +android.app.DeviceIdleFrameworkInitializer android.app.job.JobSchedulerFrameworkInitializer android.icu.impl.coll.CollationRoot android.icu.impl.IDNA2003 diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index d20cc4131261..34045c995aed 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -17,6 +17,7 @@ package android.app; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -29,6 +30,7 @@ import android.annotation.UnsupportedAppUsage; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes.AttributeUsage; import android.os.Binder; @@ -1095,21 +1097,27 @@ public class AppOpsManager { "android:sms_financial_transactions"; /** @hide Read media of audio type. */ + @SystemApi @TestApi public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; /** @hide Write media of audio type. */ + @SystemApi @TestApi public static final String OPSTR_WRITE_MEDIA_AUDIO = "android:write_media_audio"; /** @hide Read media of video type. */ + @SystemApi @TestApi public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; /** @hide Write media of video type. */ + @SystemApi @TestApi public static final String OPSTR_WRITE_MEDIA_VIDEO = "android:write_media_video"; /** @hide Read media of image type. */ + @SystemApi @TestApi public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; /** @hide Write media of image type. */ + @SystemApi @TestApi public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images"; /** @hide Has a legacy (non-isolated) view of storage. */ - @TestApi - @SystemApi + @SystemApi @TestApi public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage"; + /** @hide Interact with accessibility. */ @SystemApi public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; @@ -4281,20 +4289,17 @@ public class AppOpsManager { /** * Callback for notification of changes to operation active state. - * - * @hide */ - @TestApi public interface OnOpActiveChangedListener { /** * Called when the active state of an app op changes. * - * @param code The op code. - * @param uid The UID performing the operation. + * @param op The operation that changed. * @param packageName The package performing the operation. * @param active Whether the operation became active or inactive. */ - void onOpActiveChanged(int code, int uid, String packageName, boolean active); + void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, + boolean active); } /** @@ -4324,6 +4329,16 @@ public class AppOpsManager { public void onOpChanged(int op, String packageName) { } } + /** + * Callback for notification of changes to operation state. + * This allows you to see the raw op codes instead of strings. + * @hide + */ + public interface OnOpActiveChangedInternalListener extends OnOpActiveChangedListener { + default void onOpActiveChanged(String op, int uid, String packageName, boolean active) { } + default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { } + } + AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; @@ -4779,6 +4794,17 @@ public class AppOpsManager { } } + /** {@hide} */ + @Deprecated + public void startWatchingActive(@NonNull int[] ops, + @NonNull OnOpActiveChangedListener callback) { + final String[] strOps = new String[ops.length]; + for (int i = 0; i < ops.length; i++) { + strOps[i] = opToPublicName(ops[i]); + } + startWatchingActive(strOps, mContext.getMainExecutor(), callback); + } + /** * Start watching for changes to the active state of app ops. An app op may be * long running and it has a clear start and stop delimiters. If an op is being @@ -4786,26 +4812,25 @@ public class AppOpsManager { * watched ops for a registered callback you need to unregister and register it * again. * - * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * <p> If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS} permission * you can watch changes only for your UID. * - * @param ops The ops to watch. + * @param ops The operations to watch. * @param callback Where to report changes. * - * @see #isOperationActive(int, int, String) - * @see #stopWatchingActive(OnOpActiveChangedListener) + * @see #isOperationActive + * @see #stopWatchingActive * @see #startOp(int, int, String) * @see #finishOp(int, int, String) - * - * @hide */ - @TestApi // TODO: Uncomment below annotation once b/73559440 is fixed // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) - public void startWatchingActive(@NonNull int[] ops, + public void startWatchingActive(@NonNull String[] ops, + @CallbackExecutor @NonNull Executor executor, @NonNull OnOpActiveChangedListener callback) { - Preconditions.checkNotNull(ops, "ops cannot be null"); - Preconditions.checkNotNull(callback, "callback cannot be null"); + Objects.requireNonNull(ops); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); IAppOpsActiveCallback cb; synchronized (mActiveWatchers) { cb = mActiveWatchers.get(callback); @@ -4815,13 +4840,25 @@ public class AppOpsManager { cb = new IAppOpsActiveCallback.Stub() { @Override public void opActiveChanged(int op, int uid, String packageName, boolean active) { - callback.onOpActiveChanged(op, uid, packageName, active); + executor.execute(() -> { + if (callback instanceof OnOpActiveChangedInternalListener) { + ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op, + uid, packageName, active); + } + if (sOpToString[op] != null) { + callback.onOpActiveChanged(sOpToString[op], uid, packageName, active); + } + }); } }; mActiveWatchers.put(callback, cb); } + final int[] rawOps = new int[ops.length]; + for (int i = 0; i < ops.length; i++) { + rawOps[i] = strOpToOp(ops[i]); + } try { - mService.startWatchingActive(ops, cb); + mService.startWatchingActive(rawOps, cb); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4832,14 +4869,11 @@ public class AppOpsManager { * long running and it has a clear start and stop delimiters. Unregistering a * non-registered callback has no effect. * - * @see #isOperationActive#(int, int, String) - * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #isOperationActive + * @see #startWatchingActive * @see #startOp(int, int, String) * @see #finishOp(int, int, String) - * - * @hide */ - @TestApi public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) { synchronized (mActiveWatchers) { final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback); @@ -5449,6 +5483,19 @@ public class AppOpsManager { } /** + * Checks whether the given op for a package is active. + * <p> + * If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS} + * permission you can query only for your UID. + * + * @see #finishOp(int) + * @see #startOp(int) + */ + public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) { + return isOperationActive(strOpToOp(op), uid, packageName); + } + + /** * Checks whether the given op for a UID and package is active. * * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission diff --git a/core/java/android/app/DeviceIdleFrameworkInitializer.java b/core/java/android/app/DeviceIdleFrameworkInitializer.java new file mode 100644 index 000000000000..b304afb7ddeb --- /dev/null +++ b/core/java/android/app/DeviceIdleFrameworkInitializer.java @@ -0,0 +1,36 @@ +/* + * 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.app; + +import android.content.Context; +import android.os.DeviceIdleManager; +import android.os.IDeviceIdleController; + +/** + * This class needs to be pre-loaded by zygote. This is where the device idle manager wrapper + * is registered. + * + * @hide + */ +public class DeviceIdleFrameworkInitializer { + static { + SystemServiceRegistry.registerCachedService( + Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class, + (context, b) -> new DeviceIdleManager( + context.getOuterContext(), IDeviceIdleController.Stub.asInterface(b))); + } +} diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index c49ea09c3a25..8987b239013c 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -883,7 +883,7 @@ public final class LoadedApk { } } - // /aepx/com.android.runtime/lib, /vendor/lib, /odm/lib and /product/lib + // /apex/com.android.art/lib, /vendor/lib, /odm/lib and /product/lib // are added to the native lib search paths of the classloader. // Note that this is done AFTER the classloader is // created by ApplicationLoaders.getDefault().getClassLoader(...). The @@ -904,8 +904,8 @@ public final class LoadedApk { // (linker namespace). List<String> extraLibPaths = new ArrayList<>(4); String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : ""; - if (!defaultSearchPaths.contains("/apex/com.android.runtime/lib")) { - extraLibPaths.add("/apex/com.android.runtime/lib" + abiSuffix); + if (!defaultSearchPaths.contains("/apex/com.android.art/lib")) { + extraLibPaths.add("/apex/com.android.art/lib" + abiSuffix); } if (!defaultSearchPaths.contains("/vendor/lib")) { extraLibPaths.add("/vendor/lib" + abiSuffix); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 372eab259f4c..ac531186b974 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3278,6 +3278,7 @@ public class Notification implements Parcelable * @hide */ @Nullable + @SystemApi public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { if (actions == null) { return null; @@ -3304,7 +3305,8 @@ public class Notification implements Parcelable * * @hide */ - public List<Notification.Action> getContextualActions() { + @SystemApi + public @NonNull List<Notification.Action> getContextualActions() { if (actions == null) return Collections.emptyList(); List<Notification.Action> contextualActions = new ArrayList<>(); @@ -7730,7 +7732,8 @@ public class Notification implements Parcelable * @hide */ @Nullable - public static Message getMessageFromBundle(Bundle bundle) { + @SystemApi + public static Message getMessageFromBundle(@NonNull Bundle bundle) { try { if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { return null; diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 3effd11798fb..93e4ddcbb016 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -110,6 +110,7 @@ public final class NotificationChannel implements Parcelable { /** * @hide */ + @SystemApi public static final int USER_LOCKED_SOUND = 0x00000020; /** diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 635b9b02a944..e4fd5665d318 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -127,12 +127,10 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BugreportManager; import android.os.Build; -import android.os.DeviceIdleManager; import android.os.DropBoxManager; import android.os.HardwarePropertiesManager; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; -import android.os.IDeviceIdleController; import android.os.IDumpstate; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; @@ -195,6 +193,7 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import java.util.Map; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -1219,17 +1218,6 @@ public final class SystemServiceRegistry { } }); - registerService(Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class, - new CachedServiceFetcher<DeviceIdleManager>() { - @Override - public DeviceIdleManager createService(ContextImpl ctx) - throws ServiceNotFoundException { - IDeviceIdleController service = IDeviceIdleController.Stub.asInterface( - ServiceManager.getServiceOrThrow( - Context.DEVICE_IDLE_CONTROLLER)); - return new DeviceIdleManager(ctx.getOuterContext(), service); - }}); - registerService(Context.TIME_DETECTOR_SERVICE, TimeDetector.class, new CachedServiceFetcher<TimeDetector>() { @Override @@ -1330,9 +1318,9 @@ public final class SystemServiceRegistry { * * @hide */ - public static <T> void registerStaticService(String serviceName, Class<T> serviceClass, + public static <T> void registerStaticService(String serviceName, Class<T> serviceWrapperClass, Function<IBinder, T> serviceFetcher) { - registerService(serviceName, serviceClass, + registerService(serviceName, serviceWrapperClass, new StaticServiceFetcher<T>() { @Override public T createService() throws ServiceNotFoundException { @@ -1342,6 +1330,22 @@ public final class SystemServiceRegistry { } /** + * APEX modules will use it to register their service wrapper. + * + * @hide + */ + public static <T> void registerCachedService(String serviceName, Class<T> serviceWrapperClass, + BiFunction<ContextImpl, IBinder, T> serviceFetcher) { + registerService(serviceName, serviceWrapperClass, + new CachedServiceFetcher<T>() { + @Override + public T createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(serviceName); + return serviceFetcher.apply(ctx, b); + }}); + } + + /** * Base interface for classes that fetch services. * These objects must only be created during static initialization. */ diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ed2b99187b95..f9b96c50e0b8 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -242,7 +242,7 @@ public final class UiAutomation { mUiAutomationConnection.connect(mClient, flags); mFlags = flags; } catch (RemoteException re) { - throw new RuntimeException("Error while connecting UiAutomation", re); + throw new RuntimeException("Error while connecting " + this, re); } synchronized (mLock) { @@ -255,7 +255,7 @@ public final class UiAutomation { final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis; if (remainingTimeMillis <= 0) { - throw new RuntimeException("Error while connecting UiAutomation"); + throw new RuntimeException("Error while connecting " + this); } try { mLock.wait(remainingTimeMillis); @@ -290,7 +290,7 @@ public final class UiAutomation { synchronized (mLock) { if (mIsConnecting) { throw new IllegalStateException( - "Cannot call disconnect() while connecting!"); + "Cannot call disconnect() while connecting " + this); } throwIfNotConnectedLocked(); mConnectionId = CONNECTION_ID_UNDEFINED; @@ -299,7 +299,7 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.disconnect(); } catch (RemoteException re) { - throw new RuntimeException("Error while disconnecting UiAutomation", re); + throw new RuntimeException("Error while disconnecting " + this, re); } finally { mRemoteCallbackThread.quit(); mRemoteCallbackThread = null; @@ -1184,19 +1184,29 @@ public final class UiAutomation { return result; } + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode())); + stringBuilder.append("[id=").append(mConnectionId); + stringBuilder.append(", flags=").append(mFlags); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + private boolean isConnectedLocked() { return mConnectionId != CONNECTION_ID_UNDEFINED; } private void throwIfConnectedLocked() { if (mConnectionId != CONNECTION_ID_UNDEFINED) { - throw new IllegalStateException("UiAutomation not connected!"); + throw new IllegalStateException("UiAutomation not connected, " + this); } } private void throwIfNotConnectedLocked() { if (!isConnectedLocked()) { - throw new IllegalStateException("UiAutomation not connected!"); + throw new IllegalStateException("UiAutomation not connected, " + this); } } @@ -1220,6 +1230,9 @@ public final class UiAutomation { mConnectionId = connectionId; mLock.notifyAll(); } + if (Build.IS_DEBUGGABLE) { + Log.v(LOG_TAG, "Init " + UiAutomation.this); + } } @Override diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java index 8fb9501b3319..082663e11ea0 100644 --- a/core/java/android/content/AutofillOptions.java +++ b/core/java/android/content/AutofillOptions.java @@ -21,6 +21,8 @@ import android.annotation.TestApi; import android.app.ActivityThread; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.autofill.AutofillManager; @@ -62,6 +64,18 @@ public final class AutofillOptions implements Parcelable { @Nullable public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill; + /** + * The package disable expiration by autofill service. + */ + public long appDisabledExpiration; + + /** + * The disabled Activities of the package. key is component name string, value is when they + * will be enabled. + */ + @Nullable + public ArrayMap<String, Long> disabledActivities; + public AutofillOptions(int loggingLevel, boolean compatModeEnabled) { this.loggingLevel = loggingLevel; this.compatModeEnabled = compatModeEnabled; @@ -82,6 +96,27 @@ public final class AutofillOptions implements Parcelable { } /** + * Returns if autofill is disabled by service to the given activity. + */ + public boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { + final long elapsedTime = SystemClock.elapsedRealtime(); + final String component = componentName.flattenToString(); + // Check app first. + if (appDisabledExpiration >= elapsedTime) return true; + + // Then check activities. + if (disabledActivities != null) { + final Long expiration = disabledActivities.get(component); + if (expiration != null) { + if (expiration >= elapsedTime) return true; + disabledActivities.remove(component); + } + } + appDisabledExpiration = 0; + return false; + } + + /** * @hide */ @TestApi @@ -110,7 +145,8 @@ public final class AutofillOptions implements Parcelable { @Override public String toString() { return "AutofillOptions [loggingLevel=" + loggingLevel + ", compatMode=" + compatModeEnabled - + ", augmentedAutofillEnabled=" + augmentedAutofillEnabled + "]"; + + ", augmentedAutofillEnabled=" + augmentedAutofillEnabled + + ", appDisabledExpiration=" + appDisabledExpiration + "]"; } /** @hide */ @@ -122,6 +158,11 @@ public final class AutofillOptions implements Parcelable { pw.print(", whitelistedActivitiesForAugmentedAutofill="); pw.print(whitelistedActivitiesForAugmentedAutofill); } + pw.print(", appDisabledExpiration="); pw.print(appDisabledExpiration); + if (disabledActivities != null) { + pw.print(", disabledActivities="); + pw.print(disabledActivities); + } } @Override @@ -135,6 +176,16 @@ public final class AutofillOptions implements Parcelable { parcel.writeBoolean(compatModeEnabled); parcel.writeBoolean(augmentedAutofillEnabled); parcel.writeArraySet(whitelistedActivitiesForAugmentedAutofill); + parcel.writeLong(appDisabledExpiration); + final int size = disabledActivities != null ? disabledActivities.size() : 0; + parcel.writeInt(size); + if (size > 0) { + for (int i = 0; i < size; i++) { + final String key = disabledActivities.keyAt(i); + parcel.writeString(key); + parcel.writeLong(disabledActivities.get(key)); + } + } } public static final @android.annotation.NonNull Parcelable.Creator<AutofillOptions> CREATOR = @@ -148,6 +199,14 @@ public final class AutofillOptions implements Parcelable { options.augmentedAutofillEnabled = parcel.readBoolean(); options.whitelistedActivitiesForAugmentedAutofill = (ArraySet<ComponentName>) parcel.readArraySet(null); + options.appDisabledExpiration = parcel.readLong(); + final int size = parcel.readInt(); + if (size > 0) { + options.disabledActivities = new ArrayMap<>(); + for (int i = 0; i < size; i++) { + options.disabledActivities.put(parcel.readString(), parcel.readLong()); + } + } return options; } diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index 29acd5df4c51..0c6a9351e550 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -64,6 +64,16 @@ public class ClipDescription implements Parcelable { public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent"; /** + * The MIME type for data whose type is otherwise unknown. + * <p> + * Per RFC 2046, the "application" media type is to be used for discrete + * data which do not fit in any of the other categories, and the + * "octet-stream" subtype is used to indicate that a body contains arbitrary + * binary data. + */ + public static final String MIMETYPE_UNKNOWN = "application/octet-stream"; + + /** * The name of the extra used to define a component name when copying/dragging * an app icon from Launcher. * <p> diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 65c11d7c8ca2..f297c0631a30 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1033,10 +1033,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall /** @hide */ public final void setTransportLoggingEnabled(boolean enabled) { - if (enabled) { - mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this); - } else { - mTransport.mInterface = this; + if (mTransport != null) { + if (enabled) { + mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this); + } else { + mTransport.mInterface = this; + } } } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 9c863591f16f..1f4839380178 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -475,11 +475,9 @@ public abstract class ContentResolver implements ContentInterface { */ public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; - /** - * Default MIME type for files whose type is otherwise unknown. - * @hide - */ - public static final String MIME_TYPE_DEFAULT = "application/octet-stream"; + /** {@hide} */ + @Deprecated + public static final String MIME_TYPE_DEFAULT = ClipDescription.MIMETYPE_UNKNOWN; /** @hide */ @UnsupportedAppUsage diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java index 8223a0be559f..bdd1f4ce815f 100644 --- a/core/java/android/content/ContentValues.java +++ b/core/java/android/content/ContentValues.java @@ -259,8 +259,6 @@ public final class ContentValues implements Parcelable { * Indicates whether this collection is empty. * * @return true iff size == 0 - * {@hide} - * TODO: consider exposing this new method publicly */ public boolean isEmpty() { return mMap.isEmpty(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2c53faa5c890..a3e940b709ac 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4190,6 +4190,7 @@ public abstract class Context { * @see #getSystemService(String) * @hide */ + @TestApi public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; /** diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index fcdc81cf7533..d03bea2fb19e 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -328,6 +328,14 @@ public class UserInfo implements Parcelable { } /** @hide */ + public String toFullString() { + return "UserInfo[id=" + id + + ", name=" + name + + ", flags=" + flagsToString(flags) + + "]"; + } + + /** @hide */ public static String flagsToString(int flags) { return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags); } diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java index 611d9abb3b9f..5e31ce63d7c9 100644 --- a/core/java/android/gesture/GestureLibraries.java +++ b/core/java/android/gesture/GestureLibraries.java @@ -16,14 +16,16 @@ package android.gesture; +import android.annotation.NonNull; import android.annotation.RawRes; +import android.os.ParcelFileDescriptor; import android.util.Log; import static android.gesture.GestureConstants.*; import android.content.Context; import java.io.File; +import java.io.FileDescriptor; import java.io.FileOutputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.FileInputStream; import java.io.InputStream; @@ -41,6 +43,11 @@ public final class GestureLibraries { return new FileGestureLibrary(path); } + @NonNull + public static GestureLibrary fromFileDescriptor(@NonNull ParcelFileDescriptor pfd) { + return new FileGestureLibrary(pfd.getFileDescriptor()); + } + public static GestureLibrary fromPrivateFile(Context context, String name) { return fromFile(context.getFileStreamPath(name)); } @@ -50,55 +57,83 @@ public final class GestureLibraries { } private static class FileGestureLibrary extends GestureLibrary { + // Either a file or an fd is used private final File mPath; + private final FileDescriptor mFd; public FileGestureLibrary(File path) { mPath = path; + mFd = null; } + public FileGestureLibrary(FileDescriptor fd) { + mPath = null; + mFd = fd; + } + + /** + * <p>If this GestureLibrary was created using a FileDescriptor, + * this method will always return false. + */ @Override public boolean isReadOnly() { - return !mPath.canWrite(); + if (mPath != null) { + return !mPath.canWrite(); + } + return false; } public boolean save() { if (!mStore.hasChanged()) return true; + boolean result = false; - final File file = mPath; + if (mPath != null) { + final File file = mPath; - final File parentFile = file.getParentFile(); - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) { - return false; + final File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + if (!parentFile.mkdirs()) { + return false; + } } - } - boolean result = false; - try { - //noinspection ResultOfMethodCallIgnored - file.createNewFile(); - mStore.save(new FileOutputStream(file), true); - result = true; - } catch (FileNotFoundException e) { - Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); - } catch (IOException e) { - Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); + try { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + mStore.save(new FileOutputStream(file), true); + result = true; + } catch (IOException e) { + Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); + } + } else { + try { + mStore.save(new FileOutputStream(mFd), true); + result = true; + } catch (IOException e) { + Log.d(LOG_TAG, "Could not save the gesture library", e); + } } - return result; } public boolean load() { boolean result = false; - final File file = mPath; - if (file.exists() && file.canRead()) { + if (mPath != null) { + final File file = mPath; + if (file.exists() && file.canRead()) { + try { + mStore.load(new FileInputStream(file), true); + result = true; + } catch (IOException e) { + Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e); + } + } + } else { try { - mStore.load(new FileInputStream(file), true); + mStore.load(new FileInputStream(mFd), true); result = true; - } catch (FileNotFoundException e) { - Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e); } catch (IOException e) { - Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e); + Log.d(LOG_TAG, "Could not load the gesture library", e); } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 1b317920a935..fbcc78593aa6 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1955,6 +1955,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1973,6 +1974,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA * @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME * @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA + * @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA */ @PublicKey @NonNull diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index a0170dab9f04..2fa612574810 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -996,6 +996,14 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; + /** + * <p>The camera device is only accessible by Android's system components and privileged + * applications. Processes need to have the android.permission.SYSTEM_CAMERA in + * addition to android.permission.CAMERA in order to connect to this camera device.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java index 1b28d614a7f2..a4c65aeb1050 100644 --- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java +++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java @@ -28,7 +28,6 @@ import android.opengl.EGLSurface; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.Matrix; -import android.text.format.Time; import android.util.Log; import android.util.Pair; import android.util.Size; @@ -39,9 +38,14 @@ import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; /** * A renderer class that manages the GL state, and can draw a frame into a set of output @@ -63,6 +67,9 @@ public class SurfaceTextureRenderer { private static final int FLIP_TYPE_VERTICAL = 2; private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL; + private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss", Locale.ROOT); + private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; private EGLConfig mConfigs; @@ -624,9 +631,7 @@ public class SurfaceTextureRenderer { path.append(File.separator); path.append("durations_"); - Time now = new Time(); - now.setToNow(); - path.append(now.format2445()); + path.append(formatTimestamp(System.currentTimeMillis())); path.append("_S"); for (EGLSurfaceHolder surface : mSurfaces) { path.append(String.format("_%d_%d", surface.width, surface.height)); @@ -639,6 +644,15 @@ public class SurfaceTextureRenderer { mPerfMeasurer.dumpPerformanceData(path.toString()); } + private static String formatTimestamp(long timeMillis) { + // This is a replacement for {@link Time#format2445()} that doesn't suffer from Y2038 + // issues. + Instant instant = Instant.ofEpochMilli(timeMillis); + ZoneId zoneId = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId); + return LOG_NAME_TIME_FORMATTER.format(localDateTime); + } + private void setupGlTiming() { if (PerfMeasurement.isGlTimingSupported()) { Log.d(TAG, "Enabling GL performance measurement"); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 3462d1f56b67..6d5fe53b3f3d 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -168,8 +168,11 @@ public class Environment { return DIR_ANDROID_ROOT; } - /** {@hide} */ - @TestApi + /** + * Return root directory where all external storage devices will be mounted. + * For example, {@link #getExternalStorageDirectory()} will appear under + * this location. + */ public static @NonNull File getStorageDirectory() { return DIR_ANDROID_STORAGE; } diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index e4f88c52889f..77fd946f7ccb 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.gsi.GsiProgress; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; /** @@ -52,22 +53,39 @@ public class DynamicSystemManager { /** The DynamicSystemManager.Session represents a started session for the installation. */ public class Session { private Session() {} + /** - * Write a chunk of the DynamicSystem system image + * Set the file descriptor that points to a ashmem which will be used + * to fetch data during the submitFromAshmem. * - * @return {@code true} if the call succeeds. {@code false} if there is any native runtime - * error. + * @param ashmem fd that points to a ashmem + * @param size size of the ashmem file */ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) - public boolean write(byte[] buf) { + public boolean setAshmem(ParcelFileDescriptor ashmem, long size) { try { - return mService.write(buf); + return mService.setAshmem(ashmem, size); } catch (RemoteException e) { throw new RuntimeException(e.toString()); } } /** + * Submit bytes to the DSU partition from the ashmem previously set with + * setAshmem. + * + * @param size Number of bytes + * @return true on success, false otherwise. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) + public boolean submitFromAshmem(int size) { + try { + return mService.submitFromAshmem(size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + /** * Finish write and make device to boot into the it after reboot. * * @return {@code true} if the call succeeds. {@code false} if there is any native runtime diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl index 2f4ab2d2420d..a6de170b5ce5 100644 --- a/core/java/android/os/image/IDynamicSystemService.aidl +++ b/core/java/android/os/image/IDynamicSystemService.aidl @@ -79,10 +79,20 @@ interface IDynamicSystemService boolean setEnable(boolean enable, boolean oneShot); /** - * Write a chunk of the DynamicSystem system image + * Set the file descriptor that points to a ashmem which will be used + * to fetch data during the submitFromAshmem. * - * @return true if the call succeeds + * @param fd fd that points to a ashmem + * @param size size of the ashmem file */ - boolean write(in byte[] buf); + boolean setAshmem(in ParcelFileDescriptor fd, long size); + /** + * Submit bytes to the DSU partition from the ashmem previously set with + * setAshmem. + * + * @param bytes number of bytes that can be read from stream. + * @return true on success, false otherwise. + */ + boolean submitFromAshmem(long bytes); } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 4f7c8c5d87e2..bc03e51bd63e 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -706,16 +706,11 @@ public final class DeviceConfig { synchronized (sLock) { for (int i = 0; i < sListeners.size(); i++) { if (namespace.equals(sListeners.valueAt(i).first)) { - final int j = i; - sListeners.valueAt(i).second.execute(new Runnable() { - @Override - public void run() { - Map<String, String> propertyMap = new HashMap(1); - propertyMap.put(name, value); - sListeners.keyAt(j) - .onPropertiesChanged(new Properties(namespace, propertyMap)); - } - + final OnPropertiesChangedListener listener = sListeners.keyAt(i); + sListeners.valueAt(i).second.execute(() -> { + Map<String, String> propertyMap = new ArrayMap<>(1); + propertyMap.put(name, value); + listener.onPropertiesChanged(new Properties(namespace, propertyMap)); }); } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 2299aad6f79a..079a42ddaa6c 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -553,7 +553,7 @@ public final class MediaStore { * By default no pending items are returned. * * @see MediaColumns#IS_PENDING - * @see MediaStore#setIncludePending(Uri) + * @see MediaStore#getIncludePending(Uri) */ public static @NonNull Uri setIncludePending(@NonNull Uri uri) { return setIncludePending(uri.buildUpon()).build(); @@ -565,6 +565,17 @@ public final class MediaStore { } /** + * Return if any pending media items should be included in calls such as + * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}. + * + * @see MediaColumns#IS_PENDING + * @see MediaStore#setIncludePending(Uri) + */ + public static boolean getIncludePending(@NonNull Uri uri) { + return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_INCLUDE_PENDING)); + } + + /** * Update the given {@link Uri} to also include any trashed media items from * calls such as * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}. @@ -594,12 +605,24 @@ public final class MediaStore { * {@link UnsupportedOperationException} will be thrown when the returned * {@link Uri} is used, such as when the caller doesn't hold * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}. + * + * @see MediaStore#getRequireOriginal(Uri) */ public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) { return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build(); } /** + * Return if the caller requires the original file contents when calling + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + * + * @see MediaStore#setRequireOriginal(Uri) + */ + public static boolean getRequireOriginal(@NonNull Uri uri) { + return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL)); + } + + /** * Create a new pending media item using the given parameters. Pending items * are expected to have a short lifetime, and owners should either * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a @@ -666,45 +689,11 @@ public final class MediaStore { (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000); } - /** - * Optionally set the primary directory under which this pending item - * should be persisted. Only specific well-defined directories from - * {@link Environment} are allowed based on the media type being - * inserted. - * <p> - * For example, when creating pending {@link MediaStore.Images.Media} - * items, only {@link Environment#DIRECTORY_PICTURES} or - * {@link Environment#DIRECTORY_DCIM} are allowed. - * <p> - * You may leave this value undefined to store the media in a default - * location. For example, when this value is left undefined, pending - * {@link MediaStore.Audio.Media} items are stored under - * {@link Environment#DIRECTORY_MUSIC}. - * - * @see MediaColumns#PRIMARY_DIRECTORY - */ - public void setPrimaryDirectory(@Nullable String primaryDirectory) { - if (primaryDirectory == null) { - this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY); - } else { - this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory); - } - } - - /** - * Optionally set the secondary directory under which this pending item - * should be persisted. Any valid directory name is allowed. - * <p> - * You may leave this value undefined to store the media as a direct - * descendant of the {@link #setPrimaryDirectory(String)} location. - * - * @see MediaColumns#SECONDARY_DIRECTORY - */ - public void setSecondaryDirectory(@Nullable String secondaryDirectory) { - if (secondaryDirectory == null) { - this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY); + public void setRelativePath(@Nullable String relativePath) { + if (relativePath == null) { + this.insertValues.remove(MediaColumns.RELATIVE_PATH); } else { - this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory); + this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath); } } @@ -1470,7 +1459,14 @@ public final class MediaStore { .appendPath("downloads").build(); } - /** @hide */ + /** + * Get the content:// style URI for a single row in the downloads table + * on the given volume. + * + * @param volumeName the name of the volume to get the URI for + * @param id the download to get the URI for + * @return the URI to the downloads table on the given volume + */ public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { return ContentUris.withAppendedId(getContentUri(volumeName), id); } @@ -1795,7 +1791,14 @@ public final class MediaStore { .appendPath("media").build(); } - /** @hide */ + /** + * Get the content:// style URI for a single row in the images table + * on the given volume. + * + * @param volumeName the name of the volume to get the URI for + * @param id the image to get the URI for + * @return the URI to the images table on the given volume + */ public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { return ContentUris.withAppendedId(getContentUri(volumeName), id); } @@ -2282,7 +2285,14 @@ public final class MediaStore { .appendPath("media").build(); } - /** @hide */ + /** + * Get the content:// style URI for a single row in the audio table + * on the given volume. + * + * @param volumeName the name of the volume to get the URI for + * @param id the audio to get the URI for + * @return the URI to the audio table on the given volume + */ public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { return ContentUris.withAppendedId(getContentUri(volumeName), id); } @@ -3031,7 +3041,14 @@ public final class MediaStore { .appendPath("media").build(); } - /** @hide */ + /** + * Get the content:// style URI for a single row in the videos table + * on the given volume. + * + * @param volumeName the name of the volume to get the URI for + * @param id the video to get the URI for + * @return the URI to the videos table on the given volume + */ public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { return ContentUris.withAppendedId(getContentUri(volumeName), id); } @@ -3296,6 +3313,13 @@ public final class MediaStore { return volumeName; } + private static boolean parseBoolean(@Nullable String value) { + if (value == null) return false; + if ("1".equals(value)) return true; + if ("true".equalsIgnoreCase(value)) return true; + return false; + } + /** * Return path where the given specific volume is mounted. Not valid for * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are @@ -3592,10 +3616,23 @@ public final class MediaStore { } /** @hide */ + public static Uri scanFile(ContentProviderClient client, File file) { + return scan(client, SCAN_FILE_CALL, file, false); + } + + /** @hide */ private static Uri scan(Context context, String method, File file, boolean originatedFromShell) { final ContentResolver resolver = context.getContentResolver(); try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { + return scan(client, method, file, originatedFromShell); + } + } + + /** @hide */ + private static Uri scan(ContentProviderClient client, String method, File file, + boolean originatedFromShell) { + try { final Bundle in = new Bundle(); in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell); diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 905c7811e457..205df7e12483 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -17,6 +17,7 @@ package android.service.notification; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.Notification; import android.app.NotificationManager; @@ -172,6 +173,7 @@ public class StatusBarNotification implements Parcelable { * * @hide */ + @SystemApi public boolean isAppGroup() { if (getNotification().getGroup() != null || getNotification().getSortKey() != null) { return true; diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java new file mode 100644 index 000000000000..cc8116d09013 --- /dev/null +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -0,0 +1,168 @@ +/* + * 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.service.storage; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.os.ParcelableException; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A service to handle filesystem I/O from other apps. + * + * <p>To extend this class, you must declare the service in your manifest file with the + * {@link android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE} permission, + * and include an intent filter with the {@link #SERVICE_INTERFACE} action. + * For example:</p> + * <pre> + * <service android:name=".ExternalStorageServiceImpl" + * android:exported="true" + * android:priority="100" + * android:permission="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"> + * <intent-filter> + * <action android:name="android.service.storage.ExternalStorageService" /> + * </intent-filter> + * </service> + * </pre> + * @hide + */ +@SystemApi +public abstract class ExternalStorageService extends Service { + /** + * The Intent action that a service must respond to. Add it as an intent filter in the + * manifest declaration of the implementing service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService"; + /** + * Whether the session associated with the device file descriptor when calling + * {@link #onStartSession} is a FUSE session. + */ + public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0; + + /** + * Whether the upper file system path specified when calling {@link #onStartSession} + * should be indexed. + */ + public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1; + + /** + * {@link Bundle} key for a {@link String} value. + * + * {@hide} + */ + public static final String EXTRA_SESSION_ID = + "android.service.storage.extra.session_id"; + /** + * {@link Bundle} key for a {@link ParcelableException} value. + * + * {@hide} + */ + public static final String EXTRA_ERROR = + "android.service.storage.extra.error"; + + /** @hide */ + @IntDef(flag = true, prefix = {"FLAG_SESSION_"}, + value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE}) + @Retention(RetentionPolicy.SOURCE) + public @interface SessionFlag {} + + private final ExternalStorageServiceWrapper mWrapper = new ExternalStorageServiceWrapper(); + private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); + + /** + * Called when the system starts a session associated with {@code deviceFd} + * identified by {@code sessionId} to handle filesystem I/O for other apps. The type of + * session and other attributes are passed in {@code flag}. + * + * <p> I/O is received as requests originating from {@code upperFileSystemPath} on + * {@code deviceFd}. Implementors should handle the I/O by responding to these requests + * using the data on the {@code lowerFileSystemPath}. + * + * <p> Additional calls to start a session for the same {@code sessionId} while the session + * is still starting or already started should have no effect. + */ + public abstract void onStartSession(@NonNull String sessionId, @SessionFlag int flag, + @NonNull ParcelFileDescriptor deviceFd, @NonNull String upperFileSystemPath, + @NonNull String lowerFileSystemPath) throws IOException; + + /** + * Called when the system ends the session identified by {@code sessionId}. Implementors should + * stop handling filesystem I/O and clean up resources from the ended session. + * + * <p> Additional calls to end a session for the same {@code sessionId} while the session + * is still ending or has not started should have no effect. + */ + public abstract void onEndSession(@NonNull String sessionId) throws IOException; + + @Override + @NonNull + public final IBinder onBind(@NonNull Intent intent) { + return mWrapper; + } + + private class ExternalStorageServiceWrapper extends IExternalStorageService.Stub { + @Override + public void startSession(String sessionId, @SessionFlag int flag, + ParcelFileDescriptor deviceFd, String upperPath, String lowerPath, + RemoteCallback callback) throws RemoteException { + mHandler.post(() -> { + try { + onStartSession(sessionId, flag, deviceFd, upperPath, lowerPath); + sendResult(sessionId, null /* throwable */, callback); + } catch (Throwable t) { + sendResult(sessionId, t, callback); + } + }); + } + + @Override + public void endSession(String sessionId, RemoteCallback callback) throws RemoteException { + mHandler.post(() -> { + try { + onEndSession(sessionId); + sendResult(sessionId, null /* throwable */, callback); + } catch (Throwable t) { + sendResult(sessionId, t, callback); + } + }); + } + + private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_SESSION_ID, sessionId); + if (throwable != null) { + bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable)); + } + callback.sendResult(bundle); + } + } +} diff --git a/core/jni/android/graphics/Region.h b/core/java/android/service/storage/IExternalStorageService.aidl index 2e8e10977e78..ae46f1fc2fd2 100644 --- a/core/jni/android/graphics/Region.h +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -#ifndef _ANDROID_GRAPHICS_REGION_H_ -#define _ANDROID_GRAPHICS_REGION_H_ +package android.service.storage; -#include "jni.h" -#include "SkRegion.h" +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; -namespace android { - -/* Gets the underlying SkRegion from a Region object. */ -extern SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj); - -} // namespace android - -#endif // _ANDROID_GRAPHICS_REGION_H_ +/** + * @hide + */ +oneway interface IExternalStorageService +{ + void startSession(@utf8InCpp String sessionId, int type, in ParcelFileDescriptor deviceFd, + @utf8InCpp String upperPath, @utf8InCpp String lowerPath, in RemoteCallback callback); + void endSession(@utf8InCpp String sessionId, in RemoteCallback callback); +}
\ No newline at end of file diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 30c4e900f83c..5143f1820c2d 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -51,7 +51,6 @@ import android.view.textclassifier.TextSelection; import com.android.internal.util.Preconditions; -import java.lang.ref.WeakReference; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -431,23 +430,18 @@ public abstract class TextClassifierService extends Service { * Forwards the callback result to a wrapped binder callback. */ private static final class ProxyCallback<T extends Parcelable> implements Callback<T> { - private WeakReference<ITextClassifierCallback> mTextClassifierCallback; + private ITextClassifierCallback mTextClassifierCallback; private ProxyCallback(ITextClassifierCallback textClassifierCallback) { - mTextClassifierCallback = - new WeakReference<>(Preconditions.checkNotNull(textClassifierCallback)); + mTextClassifierCallback = Preconditions.checkNotNull(textClassifierCallback); } @Override public void onSuccess(T result) { - ITextClassifierCallback callback = mTextClassifierCallback.get(); - if (callback == null) { - return; - } try { Bundle bundle = new Bundle(1); bundle.putParcelable(KEY_RESULT, result); - callback.onSuccess(bundle); + mTextClassifierCallback.onSuccess(bundle); } catch (RemoteException e) { Slog.d(LOG_TAG, "Error calling callback"); } @@ -455,12 +449,9 @@ public abstract class TextClassifierService extends Service { @Override public void onFailure(CharSequence error) { - ITextClassifierCallback callback = mTextClassifierCallback.get(); - if (callback == null) { - return; - } try { - callback.onFailure(); + Slog.w(LOG_TAG, "Request fail: " + error); + mTextClassifierCallback.onFailure(); } catch (RemoteException e) { Slog.d(LOG_TAG, "Error calling callback"); } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 100774c896ca..44446ad04edd 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -16,6 +16,7 @@ package android.speech.tts; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RawRes; import android.annotation.SdkConstant; @@ -858,23 +859,20 @@ public class TextToSpeech { } // Post connection case - runActionNoReconnect(new Action<Void>() { - @Override - public Void run(ITextToSpeechService service) throws RemoteException { - service.setCallback(getCallerIdentity(), null); - service.stop(getCallerIdentity()); - mServiceConnection.disconnect(); - // Context#unbindService does not result in a call to - // ServiceConnection#onServiceDisconnected. As a result, the - // service ends up being destroyed (if there are no other open - // connections to it) but the process lives on and the - // ServiceConnection continues to refer to the destroyed service. - // - // This leads to tons of log spam about SynthThread being dead. - mServiceConnection = null; - mCurrentEngine = null; - return null; - } + runActionNoReconnect((ITextToSpeechService service) -> { + service.setCallback(getCallerIdentity(), null); + service.stop(getCallerIdentity()); + mServiceConnection.disconnect(); + // Context#unbindService does not result in a call to + // ServiceConnection#onServiceDisconnected. As a result, the + // service ends up being destroyed (if there are no other open + // connections to it) but the process lives on and the + // ServiceConnection continues to refer to the destroyed service. + // + // This leads to tons of log spam about SynthThread being dead. + mServiceConnection = null; + mCurrentEngine = null; + return null; }, null, "shutdown", false); } @@ -1105,17 +1103,14 @@ public class TextToSpeech { final int queueMode, final Bundle params, final String utteranceId) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - Uri utteranceUri = mUtterances.get(text); - if (utteranceUri != null) { - return service.playAudio(getCallerIdentity(), utteranceUri, queueMode, - getParams(params), utteranceId); - } else { - return service.speak(getCallerIdentity(), text, queueMode, getParams(params), - utteranceId); - } + return runAction((ITextToSpeechService service) -> { + Uri utteranceUri = mUtterances.get(text); + if (utteranceUri != null) { + return service.playAudio(getCallerIdentity(), utteranceUri, queueMode, + getParams(params), utteranceId); + } else { + return service.speak(getCallerIdentity(), text, queueMode, getParams(params), + utteranceId); } }, ERROR, "speak"); } @@ -1178,16 +1173,13 @@ public class TextToSpeech { */ public int playEarcon(final String earcon, final int queueMode, final Bundle params, final String utteranceId) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - Uri earconUri = mEarcons.get(earcon); - if (earconUri == null) { - return ERROR; - } - return service.playAudio(getCallerIdentity(), earconUri, queueMode, - getParams(params), utteranceId); + return runAction((ITextToSpeechService service) -> { + Uri earconUri = mEarcons.get(earcon); + if (earconUri == null) { + return ERROR; } + return service.playAudio(getCallerIdentity(), earconUri, queueMode, + getParams(params), utteranceId); }, ERROR, "playEarcon"); } @@ -1242,12 +1234,9 @@ public class TextToSpeech { */ public int playSilentUtterance(final long durationInMs, final int queueMode, final String utteranceId) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - return service.playSilence(getCallerIdentity(), durationInMs, - queueMode, utteranceId); - } + return runAction((ITextToSpeechService service) -> { + return service.playSilence(getCallerIdentity(), durationInMs, + queueMode, utteranceId); }, ERROR, "playSilentUtterance"); } @@ -1302,26 +1291,23 @@ public class TextToSpeech { */ @Deprecated public Set<String> getFeatures(final Locale locale) { - return runAction(new Action<Set<String>>() { - @Override - public Set<String> run(ITextToSpeechService service) throws RemoteException { - String[] features = null; - try { - features = service.getFeaturesForLanguage( - locale.getISO3Language(), locale.getISO3Country(), locale.getVariant()); - } catch(MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " + - "country code for locale: " + locale, e); - return null; - } - - if (features != null) { - final Set<String> featureSet = new HashSet<String>(); - Collections.addAll(featureSet, features); - return featureSet; - } + return runAction((ITextToSpeechService service) -> { + String[] features = null; + try { + features = service.getFeaturesForLanguage( + locale.getISO3Language(), locale.getISO3Country(), locale.getVariant()); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " + + "country code for locale: " + locale, e); return null; } + + if (features != null) { + final Set<String> featureSet = new HashSet<String>(); + Collections.addAll(featureSet, features); + return featureSet; + } + return null; }, null, "getFeatures"); } @@ -1334,11 +1320,8 @@ public class TextToSpeech { * @return {@code true} if the TTS engine is speaking. */ public boolean isSpeaking() { - return runAction(new Action<Boolean>() { - @Override - public Boolean run(ITextToSpeechService service) throws RemoteException { - return service.isSpeaking(); - } + return runAction((ITextToSpeechService service) -> { + return service.isSpeaking(); }, false, "isSpeaking"); } @@ -1349,11 +1332,8 @@ public class TextToSpeech { * @return {@link #ERROR} or {@link #SUCCESS}. */ public int stop() { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - return service.stop(getCallerIdentity()); - } + return runAction((ITextToSpeechService service) -> { + return service.stop(getCallerIdentity()); }, ERROR, "stop"); } @@ -1447,13 +1427,10 @@ public class TextToSpeech { */ @Deprecated public Locale getDefaultLanguage() { - return runAction(new Action<Locale>() { - @Override - public Locale run(ITextToSpeechService service) throws RemoteException { - String[] defaultLanguage = service.getClientDefaultLanguage(); + return runAction((ITextToSpeechService service) -> { + String[] defaultLanguage = service.getClientDefaultLanguage(); - return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]); - } + return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]); }, null, "getDefaultLanguage"); } @@ -1474,83 +1451,80 @@ public class TextToSpeech { * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. */ public int setLanguage(final Locale loc) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - if (loc == null) { + return runAction((ITextToSpeechService service) -> { + if (loc == null) { + return LANG_NOT_SUPPORTED; + } + String language = null, country = null; + try { + language = loc.getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + + try { + country = loc.getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + + String variant = loc.getVariant(); + + // As of API level 21, setLanguage is implemented using setVoice. + // (which, in the default implementation, will call loadLanguage on the service + // interface). + + // Sanitize locale using isLanguageAvailable. + int result = service.isLanguageAvailable(language, country, variant); + if (result >= LANG_AVAILABLE) { + // Get the default voice for the locale. + String voiceName = service.getDefaultVoiceNameFor(language, country, variant); + if (TextUtils.isEmpty(voiceName)) { + Log.w(TAG, "Couldn't find the default voice for " + language + "-" + + country + "-" + variant); return LANG_NOT_SUPPORTED; } - String language = null, country = null; - try { - language = loc.getISO3Language(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); + + // Load it. + if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) { + Log.w(TAG, "The service claimed " + language + "-" + country + "-" + + variant + " was available with voice name " + voiceName + + " but loadVoice returned ERROR"); return LANG_NOT_SUPPORTED; } + // Set the language/country/variant of the voice, so #getLanguage will return + // the currently set voice locale when called. + Voice voice = getVoice(service, voiceName); + if (voice == null) { + Log.w(TAG, "getDefaultVoiceNameFor returned " + voiceName + " for locale " + + language + "-" + country + "-" + variant + + " but getVoice returns null"); + return LANG_NOT_SUPPORTED; + } + String voiceLanguage = ""; try { - country = loc.getISO3Country(); + voiceLanguage = voice.getLocale().getISO3Language(); } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); - return LANG_NOT_SUPPORTED; + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); } - String variant = loc.getVariant(); - - // As of API level 21, setLanguage is implemented using setVoice. - // (which, in the default implementation, will call loadLanguage on the service - // interface). - - // Sanitize locale using isLanguageAvailable. - int result = service.isLanguageAvailable(language, country, variant); - if (result >= LANG_AVAILABLE) { - // Get the default voice for the locale. - String voiceName = service.getDefaultVoiceNameFor(language, country, variant); - if (TextUtils.isEmpty(voiceName)) { - Log.w(TAG, "Couldn't find the default voice for " + language + "-" + - country + "-" + variant); - return LANG_NOT_SUPPORTED; - } - - // Load it. - if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) { - Log.w(TAG, "The service claimed " + language + "-" + country + "-" - + variant + " was available with voice name " + voiceName - + " but loadVoice returned ERROR"); - return LANG_NOT_SUPPORTED; - } - - // Set the language/country/variant of the voice, so #getLanguage will return - // the currently set voice locale when called. - Voice voice = getVoice(service, voiceName); - if (voice == null) { - Log.w(TAG, "getDefaultVoiceNameFor returned " + voiceName + " for locale " - + language + "-" + country + "-" + variant - + " but getVoice returns null"); - return LANG_NOT_SUPPORTED; - } - String voiceLanguage = ""; - try { - voiceLanguage = voice.getLocale().getISO3Language(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + - voice.getLocale(), e); - } - - String voiceCountry = ""; - try { - voiceCountry = voice.getLocale().getISO3Country(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + - voice.getLocale(), e); - } - mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); - mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); - mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); - mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); + String voiceCountry = ""; + try { + voiceCountry = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); } - return result; + mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); + mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); + mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } + return result; }, LANG_NOT_SUPPORTED, "setLanguage"); } @@ -1582,16 +1556,13 @@ public class TextToSpeech { */ @Deprecated public Locale getLanguage() { - return runAction(new Action<Locale>() { - @Override - public Locale run(ITextToSpeechService service) { - /* No service call, but we're accessing mParams, hence need for - wrapping it as an Action instance */ - String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, ""); - String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, ""); - String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, ""); - return new Locale(lang, country, variant); - } + return runAction((ITextToSpeechService service) -> { + /* No service call, but we're accessing mParams, hence need for + wrapping it as an Action instance */ + String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, ""); + String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, ""); + String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, ""); + return new Locale(lang, country, variant); }, null, "getLanguage"); } @@ -1599,19 +1570,16 @@ public class TextToSpeech { * Query the engine about the set of available languages. */ public Set<Locale> getAvailableLanguages() { - return runAction(new Action<Set<Locale>>() { - @Override - public Set<Locale> run(ITextToSpeechService service) throws RemoteException { - List<Voice> voices = service.getVoices(); - if (voices == null) { - return new HashSet<Locale>(); - } - HashSet<Locale> locales = new HashSet<Locale>(); - for (Voice voice : voices) { - locales.add(voice.getLocale()); - } - return locales; + return runAction((ITextToSpeechService service) -> { + List<Voice> voices = service.getVoices(); + if (voices == null) { + return new HashSet<Locale>(); + } + HashSet<Locale> locales = new HashSet<Locale>(); + for (Voice voice : voices) { + locales.add(voice.getLocale()); } + return locales; }, null, "getAvailableLanguages"); } @@ -1625,12 +1593,9 @@ public class TextToSpeech { * @see Voice */ public Set<Voice> getVoices() { - return runAction(new Action<Set<Voice>>() { - @Override - public Set<Voice> run(ITextToSpeechService service) throws RemoteException { - List<Voice> voices = service.getVoices(); - return (voices != null) ? new HashSet<Voice>(voices) : new HashSet<Voice>(); - } + return runAction((ITextToSpeechService service) -> { + List<Voice> voices = service.getVoices(); + return (voices != null) ? new HashSet<Voice>(voices) : new HashSet<Voice>(); }, null, "getVoices"); } @@ -1645,36 +1610,33 @@ public class TextToSpeech { * @see Voice */ public int setVoice(final Voice voice) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - int result = service.loadVoice(getCallerIdentity(), voice.getName()); - if (result == SUCCESS) { - mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName()); - - // Set the language/country/variant, so #getLanguage will return the voice - // locale when called. - String language = ""; - try { - language = voice.getLocale().getISO3Language(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + - voice.getLocale(), e); - } + return runAction((ITextToSpeechService service) -> { + int result = service.loadVoice(getCallerIdentity(), voice.getName()); + if (result == SUCCESS) { + mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName()); + + // Set the language/country/variant, so #getLanguage will return the voice + // locale when called. + String language = ""; + try { + language = voice.getLocale().getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); + } - String country = ""; - try { - country = voice.getLocale().getISO3Country(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + - voice.getLocale(), e); - } - mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); - mParams.putString(Engine.KEY_PARAM_COUNTRY, country); - mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); + String country = ""; + try { + country = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); } - return result; + mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); + mParams.putString(Engine.KEY_PARAM_COUNTRY, country); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } + return result; }, LANG_NOT_SUPPORTED, "setVoice"); } @@ -1689,15 +1651,12 @@ public class TextToSpeech { * @see Voice */ public Voice getVoice() { - return runAction(new Action<Voice>() { - @Override - public Voice run(ITextToSpeechService service) throws RemoteException { - String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, ""); - if (TextUtils.isEmpty(voiceName)) { - return null; - } - return getVoice(service, voiceName); + return runAction((ITextToSpeechService service) -> { + String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, ""); + if (TextUtils.isEmpty(voiceName)) { + return null; } + return getVoice(service, voiceName); }, null, "getVoice"); } @@ -1730,45 +1689,42 @@ public class TextToSpeech { * on error. */ public Voice getDefaultVoice() { - return runAction(new Action<Voice>() { - @Override - public Voice run(ITextToSpeechService service) throws RemoteException { + return runAction((ITextToSpeechService service) -> { - String[] defaultLanguage = service.getClientDefaultLanguage(); + String[] defaultLanguage = service.getClientDefaultLanguage(); - if (defaultLanguage == null || defaultLanguage.length == 0) { - Log.e(TAG, "service.getClientDefaultLanguage() returned empty array"); - return null; - } - String language = defaultLanguage[0]; - String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : ""; - String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : ""; - - // Sanitize the locale using isLanguageAvailable. - int result = service.isLanguageAvailable(language, country, variant); - if (result < LANG_AVAILABLE) { - // The default language is not supported. - return null; - } + if (defaultLanguage == null || defaultLanguage.length == 0) { + Log.e(TAG, "service.getClientDefaultLanguage() returned empty array"); + return null; + } + String language = defaultLanguage[0]; + String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : ""; + String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : ""; + + // Sanitize the locale using isLanguageAvailable. + int result = service.isLanguageAvailable(language, country, variant); + if (result < LANG_AVAILABLE) { + // The default language is not supported. + return null; + } - // Get the default voice name - String voiceName = service.getDefaultVoiceNameFor(language, country, variant); - if (TextUtils.isEmpty(voiceName)) { - return null; - } + // Get the default voice name + String voiceName = service.getDefaultVoiceNameFor(language, country, variant); + if (TextUtils.isEmpty(voiceName)) { + return null; + } - // Find it - List<Voice> voices = service.getVoices(); - if (voices == null) { - return null; - } - for (Voice voice : voices) { - if (voice.getName().equals(voiceName)) { - return voice; - } - } + // Find it + List<Voice> voices = service.getVoices(); + if (voices == null) { return null; } + for (Voice voice : voices) { + if (voice.getName().equals(voiceName)) { + return voice; + } + } + return null; }, null, "getDefaultVoice"); } @@ -1784,31 +1740,55 @@ public class TextToSpeech { * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. */ public int isLanguageAvailable(final Locale loc) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - String language = null, country = null; + return runAction((ITextToSpeechService service) -> { + String language = null, country = null; - try { - language = loc.getISO3Language(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); - return LANG_NOT_SUPPORTED; - } - - try { - country = loc.getISO3Country(); - } catch (MissingResourceException e) { - Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); - return LANG_NOT_SUPPORTED; - } + try { + language = loc.getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } - return service.isLanguageAvailable(language, country, loc.getVariant()); + try { + country = loc.getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; } + + return service.isLanguageAvailable(language, country, loc.getVariant()); }, LANG_NOT_SUPPORTED, "isLanguageAvailable"); } /** + * Synthesizes the given text to a ParcelFileDescriptor using the specified parameters. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. In order to reliably detect errors during synthesis, + * we recommend setting an utterance progress listener (see + * {@link #setOnUtteranceProgressListener}). + * + * @param text The text that should be synthesized. No longer than + * {@link #getMaxSpeechInputLength()} characters. + * @param params Parameters for the request. Can be null. + * Engine specific parameters may be passed in but the parameter keys + * must be prefixed by the name of the engine they are intended for. For example + * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the engine + * named "com.svox.pico" if it is being used. + * @param fileDescriptor ParcelFileDescriptor to write the generated audio data to. + * @param utteranceId An unique identifier for this request. + * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation. + */ + public int synthesizeToFile(@NonNull final CharSequence text, @NonNull final Bundle params, + @NonNull final ParcelFileDescriptor fileDescriptor, @NonNull final String utteranceId) { + return runAction((ITextToSpeechService service) -> { + return service.synthesizeToFileDescriptor(getCallerIdentity(), text, + fileDescriptor, getParams(params), utteranceId); + }, ERROR, "synthesizeToFile"); + } + + /** * Synthesizes the given text to a file using the specified parameters. * This method is asynchronous, i.e. the method just adds the request to the queue of TTS * requests and then returns. The synthesis might not have finished (or even started!) at the @@ -1829,33 +1809,26 @@ public class TextToSpeech { */ public int synthesizeToFile(final CharSequence text, final Bundle params, final File file, final String utteranceId) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - ParcelFileDescriptor fileDescriptor; - int returnValue; - try { - if(file.exists() && !file.canWrite()) { - Log.e(TAG, "Can't write to " + file); - return ERROR; - } - fileDescriptor = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_WRITE_ONLY | - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE); - returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text, - fileDescriptor, getParams(params), utteranceId); - fileDescriptor.close(); - return returnValue; - } catch (FileNotFoundException e) { - Log.e(TAG, "Opening file " + file + " failed", e); - return ERROR; - } catch (IOException e) { - Log.e(TAG, "Closing file " + file + " failed", e); - return ERROR; - } - } - }, ERROR, "synthesizeToFile"); + if (file.exists() && !file.canWrite()) { + Log.e(TAG, "Can't write to " + file); + return ERROR; + } + try ( + ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_WRITE_ONLY + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE); + ) { + int returnValue = synthesizeToFile(text, params, fileDescriptor, utteranceId); + fileDescriptor.close(); + return returnValue; + } catch (FileNotFoundException e) { + Log.e(TAG, "Opening file " + file + " failed", e); + return ERROR; + } catch (IOException e) { + Log.e(TAG, "Closing file " + file + " failed", e); + return ERROR; + } } /** diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index e94b8006d24a..b00a9385eec8 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -32,6 +32,9 @@ import libcore.icu.LocaleData; import libcore.icu.RelativeDateTimeFormatter; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Calendar; import java.util.Date; import java.util.Formatter; @@ -502,17 +505,21 @@ public class DateUtils * @return true if the supplied when is today else false */ public static boolean isToday(long when) { - Time time = new Time(); - time.set(when); + return isSameDate(when, System.currentTimeMillis()); + } + + private static boolean isSameDate(long oneMillis, long twoMillis) { + ZoneId zoneId = ZoneId.systemDefault(); + + Instant oneInstant = Instant.ofEpochMilli(oneMillis); + LocalDateTime oneLocalDateTime = LocalDateTime.ofInstant(oneInstant, zoneId); - int thenYear = time.year; - int thenMonth = time.month; - int thenMonthDay = time.monthDay; + Instant twoInstant = Instant.ofEpochMilli(twoMillis); + LocalDateTime twoLocalDateTime = LocalDateTime.ofInstant(twoInstant, zoneId); - time.set(System.currentTimeMillis()); - return (thenYear == time.year) - && (thenMonth == time.month) - && (thenMonthDay == time.monthDay); + return (oneLocalDateTime.getYear() == twoLocalDateTime.getYear()) + && (oneLocalDateTime.getMonthValue() == twoLocalDateTime.getMonthValue()) + && (oneLocalDateTime.getDayOfMonth() == twoLocalDateTime.getDayOfMonth()); } /** diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index d899dde9ef59..3f44e48d8efb 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -48,6 +48,8 @@ import java.util.Set; * you have no control over this shrinking -- if you set a capacity and then remove an * item, it may reduce the capacity to better match the current size. In the future an * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p> + * + * <p>This structure is <b>NOT</b> thread-safe.</p> */ public final class ArrayMap<K, V> implements Map<K, V> { private static final boolean DEBUG = false; @@ -103,15 +105,21 @@ public final class ArrayMap<K, V> implements Map<K, V> { static Object[] mTwiceBaseCache; @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static int mTwiceBaseCacheSize; + /** + * Separate locks for each cache since each can be accessed independently of the other without + * risk of a deadlock. + */ + private static final Object sBaseCacheLock = new Object(); + private static final Object sTwiceBaseCacheLock = new Object(); - final boolean mIdentityHashCode; + private final boolean mIdentityHashCode; @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API. int[] mHashes; @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API. Object[] mArray; @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() int mSize; - MapCollections<K, V> mCollections; + private MapCollections<K, V> mCollections; private static int binarySearchHashes(int[] hashes, int N, int hash) { try { @@ -209,31 +217,57 @@ public final class ArrayMap<K, V> implements Map<K, V> { throw new UnsupportedOperationException("ArrayMap is immutable"); } if (size == (BASE_SIZE*2)) { - synchronized (ArrayMap.class) { + synchronized (sTwiceBaseCacheLock) { if (mTwiceBaseCache != null) { final Object[] array = mTwiceBaseCache; mArray = array; - mTwiceBaseCache = (Object[])array[0]; - mHashes = (int[])array[1]; - array[0] = array[1] = null; - mTwiceBaseCacheSize--; - if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes - + " now have " + mTwiceBaseCacheSize + " entries"); - return; + try { + mTwiceBaseCache = (Object[]) array[0]; + mHashes = (int[]) array[1]; + if (mHashes != null) { + array[0] = array[1] = null; + mTwiceBaseCacheSize--; + if (DEBUG) { + Log.d(TAG, "Retrieving 2x cache " + mHashes + + " now have " + mTwiceBaseCacheSize + " entries"); + } + return; + } + } catch (ClassCastException e) { + } + // Whoops! Someone trampled the array (probably due to not protecting + // their access with a lock). Our cache is corrupt; report and give up. + Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0] + + " [1]=" + array[1]); + mTwiceBaseCache = null; + mTwiceBaseCacheSize = 0; } } } else if (size == BASE_SIZE) { - synchronized (ArrayMap.class) { + synchronized (sBaseCacheLock) { if (mBaseCache != null) { final Object[] array = mBaseCache; mArray = array; - mBaseCache = (Object[])array[0]; - mHashes = (int[])array[1]; - array[0] = array[1] = null; - mBaseCacheSize--; - if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes - + " now have " + mBaseCacheSize + " entries"); - return; + try { + mBaseCache = (Object[]) array[0]; + mHashes = (int[]) array[1]; + if (mHashes != null) { + array[0] = array[1] = null; + mBaseCacheSize--; + if (DEBUG) { + Log.d(TAG, "Retrieving 1x cache " + mHashes + + " now have " + mBaseCacheSize + " entries"); + } + return; + } + } catch (ClassCastException e) { + } + // Whoops! Someone trampled the array (probably due to not protecting + // their access with a lock). Our cache is corrupt; report and give up. + Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0] + + " [1]=" + array[1]); + mBaseCache = null; + mBaseCacheSize = 0; } } } @@ -242,10 +276,14 @@ public final class ArrayMap<K, V> implements Map<K, V> { mArray = new Object[size<<1]; } + /** + * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other + * words, don't pass mHashes or mArray in directly. + */ @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE*2)) { - synchronized (ArrayMap.class) { + synchronized (sTwiceBaseCacheLock) { if (mTwiceBaseCacheSize < CACHE_SIZE) { array[0] = mTwiceBaseCache; array[1] = hashes; @@ -259,7 +297,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { } } } else if (hashes.length == BASE_SIZE) { - synchronized (ArrayMap.class) { + synchronized (sBaseCacheLock) { if (mBaseCacheSize < CACHE_SIZE) { array[0] = mBaseCache; array[1] = hashes; diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java index 3fa914f9ad02..44c5af20d891 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -23,6 +23,7 @@ import libcore.util.EmptyArray; import java.lang.reflect.Array; import java.util.Collection; +import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -46,6 +47,8 @@ import java.util.function.Predicate; * you have no control over this shrinking -- if you set a capacity and then remove an * item, it may reduce the capacity to better match the current size. In the future an * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p> + * + * <p>This structure is <b>NOT</b> thread-safe.</p> */ public final class ArraySet<E> implements Collection<E>, Set<E> { private static final boolean DEBUG = false; @@ -72,15 +75,30 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { static int sBaseCacheSize; static Object[] sTwiceBaseCache; static int sTwiceBaseCacheSize; + /** + * Separate locks for each cache since each can be accessed independently of the other without + * risk of a deadlock. + */ + private static final Object sBaseCacheLock = new Object(); + private static final Object sTwiceBaseCacheLock = new Object(); - final boolean mIdentityHashCode; + private final boolean mIdentityHashCode; @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API. int[] mHashes; @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API. Object[] mArray; @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() int mSize; - MapCollections<E, E> mCollections; + private MapCollections<E, E> mCollections; + + private int binarySearch(int[] hashes, int hash) { + try { + return ContainerHelpers.binarySearch(hashes, mSize, hash); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ConcurrentModificationException(); + } + } + @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object). private int indexOf(Object key, int hash) { @@ -91,7 +109,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return ~0; } - int index = ContainerHelpers.binarySearch(mHashes, N, hash); + int index = binarySearch(mHashes, hash); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { @@ -130,7 +148,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return ~0; } - int index = ContainerHelpers.binarySearch(mHashes, N, 0); + int index = binarySearch(mHashes, 0); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { @@ -163,20 +181,22 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private void allocArrays(final int size) { if (size == (BASE_SIZE * 2)) { - synchronized (ArraySet.class) { + synchronized (sTwiceBaseCacheLock) { if (sTwiceBaseCache != null) { final Object[] array = sTwiceBaseCache; try { mArray = array; sTwiceBaseCache = (Object[]) array[0]; mHashes = (int[]) array[1]; - array[0] = array[1] = null; - sTwiceBaseCacheSize--; - if (DEBUG) { - Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have " - + sTwiceBaseCacheSize + " entries"); - } - return; + if (mHashes != null) { + array[0] = array[1] = null; + sTwiceBaseCacheSize--; + if (DEBUG) { + Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have " + + sTwiceBaseCacheSize + " entries"); + } + return; + } } catch (ClassCastException e) { } // Whoops! Someone trampled the array (probably due to not protecting @@ -188,20 +208,22 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } } } else if (size == BASE_SIZE) { - synchronized (ArraySet.class) { + synchronized (sBaseCacheLock) { if (sBaseCache != null) { final Object[] array = sBaseCache; try { mArray = array; sBaseCache = (Object[]) array[0]; mHashes = (int[]) array[1]; - array[0] = array[1] = null; - sBaseCacheSize--; - if (DEBUG) { - Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize - + " entries"); + if (mHashes != null) { + array[0] = array[1] = null; + sBaseCacheSize--; + if (DEBUG) { + Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + + sBaseCacheSize + " entries"); + } + return; } - return; } catch (ClassCastException e) { } // Whoops! Someone trampled the array (probably due to not protecting @@ -218,10 +240,14 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { mArray = new Object[size]; } + /** + * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other + * words, don't pass mHashes or mArray in directly. + */ @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE * 2)) { - synchronized (ArraySet.class) { + synchronized (sTwiceBaseCacheLock) { if (sTwiceBaseCacheSize < CACHE_SIZE) { array[0] = sTwiceBaseCache; array[1] = hashes; @@ -237,7 +263,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } } } else if (hashes.length == BASE_SIZE) { - synchronized (ArraySet.class) { + synchronized (sBaseCacheLock) { if (sBaseCacheSize < CACHE_SIZE) { array[0] = sBaseCache; array[1] = hashes; @@ -308,10 +334,16 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { @Override public void clear() { if (mSize != 0) { - freeArrays(mHashes, mArray, mSize); + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + final int osize = mSize; mHashes = EmptyArray.INT; mArray = EmptyArray.OBJECT; mSize = 0; + freeArrays(ohashes, oarray, osize); + } + if (mSize != 0) { + throw new ConcurrentModificationException(); } } @@ -320,6 +352,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * items. */ public void ensureCapacity(int minimumCapacity) { + final int oSize = mSize; if (mHashes.length < minimumCapacity) { final int[] ohashes = mHashes; final Object[] oarray = mArray; @@ -330,6 +363,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } freeArrays(ohashes, oarray, mSize); } + if (mSize != oSize) { + throw new ConcurrentModificationException(); + } } /** @@ -400,11 +436,10 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * * @param value the object to add. * @return {@code true} if this set is modified, {@code false} otherwise. - * @throws ClassCastException - * when the class of the object is inappropriate for this set. */ @Override public boolean add(E value) { + final int oSize = mSize; final int hash; int index; if (value == null) { @@ -419,9 +454,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } index = ~index; - if (mSize >= mHashes.length) { - final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) - : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE); + if (oSize >= mHashes.length) { + final int n = oSize >= (BASE_SIZE * 2) ? (oSize + (oSize >> 1)) + : (oSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE); if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n); @@ -429,21 +464,29 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { final Object[] oarray = mArray; allocArrays(n); + if (oSize != mSize) { + throw new ConcurrentModificationException(); + } + if (mHashes.length > 0) { - if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0"); + if (DEBUG) Log.d(TAG, "add: copy 0-" + oSize + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); System.arraycopy(oarray, 0, mArray, 0, oarray.length); } - freeArrays(ohashes, oarray, mSize); + freeArrays(ohashes, oarray, oSize); } - if (index < mSize) { + if (index < oSize) { if (DEBUG) { - Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1)); + Log.d(TAG, "add: move " + index + "-" + (oSize - index) + " to " + (index + 1)); } - System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); - System.arraycopy(mArray, index, mArray, index + 1, mSize - index); + System.arraycopy(mHashes, index, mHashes, index + 1, oSize - index); + System.arraycopy(mArray, index, mArray, index + 1, oSize - index); + } + + if (oSize != mSize || index >= mHashes.length) { + throw new ConcurrentModificationException(); } mHashes[index] = hash; @@ -458,6 +501,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @hide */ public void append(E value) { + final int oSize = mSize; final int index = mSize; final int hash = value == null ? 0 : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode()); @@ -476,6 +520,11 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { add(value); return; } + + if (oSize != mSize) { + throw new ConcurrentModificationException(); + } + mSize = index + 1; mHashes[index] = hash; mArray[index] = value; @@ -492,6 +541,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { if (N > 0) { System.arraycopy(array.mHashes, 0, mHashes, 0, N); System.arraycopy(array.mArray, 0, mArray, 0, N); + if (0 != mSize) { + throw new ConcurrentModificationException(); + } mSize = N; } } else { @@ -549,12 +601,14 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { // Check if exception should be thrown outside of the critical path. throw new ArrayIndexOutOfBoundsException(index); } + final int oSize = mSize; final Object old = mArray[index]; - if (mSize <= 1) { + if (oSize <= 1) { // Now empty. if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); clear(); } else { + final int nSize = oSize - 1; if (shouldShrink()) { // Shrunk enough to reduce size of arrays. final int n = getNewShrunkenSize(); @@ -565,31 +619,33 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { final Object[] oarray = mArray; allocArrays(n); - mSize--; if (index > 0) { if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, index); System.arraycopy(oarray, 0, mArray, 0, index); } - if (index < mSize) { + if (index < nSize) { if (DEBUG) { - Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize + Log.d(TAG, "remove: copy from " + (index + 1) + "-" + nSize + " to " + index); } - System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); - System.arraycopy(oarray, index + 1, mArray, index, mSize - index); + System.arraycopy(ohashes, index + 1, mHashes, index, nSize - index); + System.arraycopy(oarray, index + 1, mArray, index, nSize - index); } } else { - mSize--; - if (index < mSize) { + if (index < nSize) { if (DEBUG) { - Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index); + Log.d(TAG, "remove: move " + (index + 1) + "-" + nSize + " to " + index); } - System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); - System.arraycopy(mArray, index + 1, mArray, index, mSize - index); + System.arraycopy(mHashes, index + 1, mHashes, index, nSize - index); + System.arraycopy(mArray, index + 1, mArray, index, nSize - index); } - mArray[mSize] = null; + mArray[nSize] = null; + } + if (oSize != mSize) { + throw new ConcurrentModificationException(); } + mSize = nSize; } return (E) old; } diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index f8b38e9d215d..07cecd38620d 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -62,9 +62,9 @@ public class TimeUtils { } /** - * Tries to return a frozen ICU time zone that would have had the specified offset - * and DST value at the specified moment in the specified country. - * Returns null if no suitable zone could be found. + * Returns a frozen ICU time zone that has / would have had the specified offset and DST value + * at the specified moment in the specified country. Returns null if no suitable zone could be + * found. */ private static android.icu.util.TimeZone getIcuTimeZone( int offset, boolean dst, long when, String country) { @@ -73,8 +73,15 @@ public class TimeUtils { } android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault(); - return TimeZoneFinder.getInstance() - .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias); + CountryTimeZones countryTimeZones = + TimeZoneFinder.getInstance().lookupCountryTimeZones(country); + if (countryTimeZones == null) { + return null; + } + + CountryTimeZones.OffsetResult offsetResult = + countryTimeZones.lookupByOffsetWithBias(offset, dst, when, bias); + return offsetResult != null ? offsetResult.mTimeZone : null; } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 1f7ae0e7e245..3bbd3213409a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -43,6 +43,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; +import android.os.SystemClock; import android.service.autofill.AutofillService; import android.service.autofill.FillEventHistory; import android.service.autofill.UserData; @@ -1737,6 +1738,26 @@ public final class AutofillManager { final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); final ComponentName componentName = client.autofillClientGetComponentName(); + + if (!mEnabledForAugmentedAutofillOnly && mOptions != null + && mOptions.isAutofillDisabledLocked(componentName)) { + if (mOptions.isAugmentedAutofillEnabled(mContext)) { + if (sDebug) { + Log.d(TAG, "startSession(" + componentName + "): disabled by service but " + + "whitelisted for augmented autofill"); + flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; + } + } else { + if (sDebug) { + Log.d(TAG, "startSession(" + componentName + "): ignored because " + + "disabled by service and not whitelisted for augmented autofill"); + } + setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, null); + client.autofillClientResetableStateAvailable(); + return; + } + } + mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, componentName, @@ -2067,6 +2088,7 @@ public final class AutofillManager { mServiceClientCleaner.clean(); mServiceClientCleaner = null; } + notifyReenableAutofill(); } } sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; @@ -2403,6 +2425,37 @@ public final class AutofillManager { } } + private void notifyDisableAutofill(long disableDuration, ComponentName componentName) { + synchronized (mLock) { + if (mOptions == null) { + return; + } + long expiration = SystemClock.elapsedRealtime() + disableDuration; + // Protect it against overflow + if (expiration < 0) { + expiration = Long.MAX_VALUE; + } + if (componentName != null) { + if (mOptions.disabledActivities == null) { + mOptions.disabledActivities = new ArrayMap<>(); + } + mOptions.disabledActivities.put(componentName.flattenToString(), expiration); + } else { + mOptions.appDisabledExpiration = expiration; + } + } + } + + void notifyReenableAutofill() { + synchronized (mLock) { + if (mOptions == null) { + return; + } + mOptions.appDisabledExpiration = 0; + mOptions.disabledActivities = null; + } + } + private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { if (sVerbose) { Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id @@ -3181,6 +3234,15 @@ public final class AutofillManager { } @Override + public void notifyDisableAutofill(long disableDuration, ComponentName componentName) + throws RemoteException { + final AutofillManager afm = mAfm.get(); + if (afm != null) { + afm.post(() -> afm.notifyDisableAutofill(disableDuration, componentName)); + } + } + + @Override public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) { final AutofillManager afm = mAfm.get(); if (afm != null) { diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 85def7fa7510..84949c87377b 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -18,6 +18,7 @@ package android.view.autofill; import java.util.List; +import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; @@ -111,4 +112,8 @@ oneway interface IAutoFillManagerClient { */ void getAugmentedAutofillClient(in IResultReceiver result); + /** + * Notifies disables autofill for the app or activity. + */ + void notifyDisableAutofill(long disableDuration, in ComponentName componentName); } diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index a97c3305208a..66f75043a9c8 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -309,7 +309,7 @@ public final class SystemTextClassifier implements TextClassifier { } public void onFailure() { - Log.e(LOG_TAG, "Request failed.", null); + Log.e(LOG_TAG, "Request failed at " + mName, null); mLatch.countDown(); } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 51ca80524090..d0f80936f477 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -999,6 +999,7 @@ public final class SelectionActionModeHelper { } private void onTimeOut() { + Log.d(LOG_TAG, "Timeout in TextClassificationAsyncTask"); if (getStatus() == Status.RUNNING) { onPostExecute(mTimeOutResultSupplier.get()); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 88d0e8534a0a..8503ab82ac48 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -4750,11 +4750,11 @@ public class BatteryStatsImpl extends BatteryStats { final long uptime = mClocks.uptimeMillis(); boolean updateHistory = false; - if (isScreenDoze(state)) { + if (isScreenDoze(state) && !isScreenDoze(oldState)) { mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.startRunningLocked(elapsedRealtime); updateHistory = true; - } else if (isScreenDoze(oldState)) { + } else if (isScreenDoze(oldState) && !isScreenDoze(state)) { mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.stopRunningLocked(elapsedRealtime); updateHistory = true; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index fb68bc762245..4c5453954c62 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -339,6 +339,8 @@ cc_library_static { cppflags: ["-Wno-conversion-null"], srcs: [ + "android/graphics/apex/android_region.cpp", + "android_graphics_Canvas.cpp", "android_graphics_ColorSpace.cpp", "android_graphics_drawable_AnimatedVectorDrawable.cpp", @@ -386,10 +388,12 @@ cc_library_static { local_include_dirs: [ "include", // NEEDED FOR ANDROID RUNTIME "android/graphics", + "android/graphics/apex/include", ], export_include_dirs: [ ".", + "android/graphics/apex/include", ], include_dirs: [ @@ -417,6 +421,8 @@ cc_library_static { target: { android: { srcs: [ // sources that depend on android only libraries + "android/graphics/apex/android_canvas.cpp", + "android_view_TextureLayer.cpp", "android_view_ThreadedRenderer.cpp", "android/graphics/BitmapRegionDecoder.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index a3fd915c29b2..c12940a4d582 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -245,6 +245,9 @@ static const char* kGenerationalCCRuntimeOption = "-Xgc:generational_cc"; // Copying (CC) garbage collector. static const char* kNoGenerationalCCRuntimeOption = "-Xgc:nogenerational_cc"; +// Phenotype property name for enabling profiling the boot class path. +static const char* PROFILE_BOOT_CLASS_PATH = "profilebootclasspath"; + // Feature flag name for running the JIT in Zygote experiment, b/119800099. static const char* ENABLE_APEX_IMAGE = "enable_apex_image"; // Flag to pass to the runtime when using the apex image. @@ -690,6 +693,24 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX]; char bootImageBuf[sizeof("-Ximage:") - 1 + PROPERTY_VALUE_MAX]; + // Read if we are using the profile configuration, do this at the start since the last ART args + // take precedence. + property_get("dalvik.vm.profilebootclasspath", propBuf, ""); + std::string profile_boot_class_path = propBuf; + // Empty means the property is unset and we should default to the phenotype property. + // The possible values are {"true", "false", ""} + if (profile_boot_class_path.empty()) { + profile_boot_class_path = server_configurable_flags::GetServerConfigurableFlag( + RUNTIME_NATIVE_BOOT_NAMESPACE, + PROFILE_BOOT_CLASS_PATH, + /*default_value=*/ ""); + } + if (profile_boot_class_path == "true") { + addOption("-Xps-profile-boot-class-path"); + addOption("-Xps-profile-aot-code"); + addOption("-Xjitsaveprofilinginfo"); + } + std::string use_apex_image = server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, ENABLE_APEX_IMAGE, @@ -807,13 +828,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.jittransitionweight", jittransitionweightOptBuf, "-Xjittransitionweight:"); - - property_get("dalvik.vm.profilebootimage", propBuf, ""); - if (strcmp(propBuf, "true") == 0) { - addOption("-Xps-profile-boot-class-path"); - addOption("-Xps-profile-aot-code"); - } - /* * Madvise related options. */ diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp index 90ca3bbef104..1fb5fe3b99f1 100644 --- a/core/jni/android/graphics/Region.cpp +++ b/core/jni/android/graphics/Region.cpp @@ -360,8 +360,4 @@ int register_android_graphics_Region(JNIEnv* env) NELEM(gRegionIterMethods)); } -SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj) { - return GetSkRegion(env, regionObj); -} - } // namespace android diff --git a/core/jni/android/graphics/apex/android_canvas.cpp b/core/jni/android/graphics/apex/android_canvas.cpp new file mode 100644 index 000000000000..7a4495f4f259 --- /dev/null +++ b/core/jni/android/graphics/apex/android_canvas.cpp @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#include "android/graphics/canvas.h" + +#include "GraphicsJNI.h" + +#include <hwui/Canvas.h> +#include <utils/Color.h> + +#include <SkBitmap.h> + +using namespace android; + +static inline Canvas* toCanvas(ACanvas* aCanvas) { + return reinterpret_cast<Canvas*>(aCanvas); +} + +static inline ACanvas* toACanvas(Canvas* canvas) { + return reinterpret_cast<ACanvas*>(canvas); +} + +bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat) { + ANativeWindow_Buffer buffer { 0, 0, 0, bufferFormat, nullptr, {0} }; + const SkColorType colorType = uirenderer::ANativeWindowToImageInfo(buffer, nullptr).colorType(); + return kUnknown_SkColorType != colorType; +} + +ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) { + return toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj)); +} + +void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace) { + SkBitmap bitmap; + if (buffer != nullptr && buffer->width > 0 && buffer->height > 0) { + sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace)); + SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs); + ssize_t rowBytes = buffer->stride * imageInfo.bytesPerPixel(); + bitmap.setInfo(imageInfo, rowBytes); + bitmap.setPixels(buffer->bits); + } + + toCanvas(canvas)->setBitmap(bitmap); +} + +void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) { + //TODO update Canvas to take antialias param + toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, + SkClipOp::kIntersect); +} + +void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) { + //TODO update Canvas to take antialias param + toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, + SkClipOp::kDifference); +} diff --git a/core/jni/android/graphics/apex/android_region.cpp b/core/jni/android/graphics/apex/android_region.cpp new file mode 100644 index 000000000000..2030e7e69861 --- /dev/null +++ b/core/jni/android/graphics/apex/android_region.cpp @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#include "android/graphics/region.h" + +#include "GraphicsJNI.h" + +#include <SkRegion.h> + +static inline SkRegion::Iterator* ARegionIter_to_SkRegionIter(ARegionIterator* iterator) { + return reinterpret_cast<SkRegion::Iterator*>(iterator); +} + +static inline ARegionIterator* SkRegionIter_to_ARegionIter(SkRegion::Iterator* iterator) { + return reinterpret_cast<ARegionIterator*>(iterator); +} + +ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject regionObj) { + SkRegion* region = GraphicsJNI::getNativeRegion(env, regionObj); + return (!region) ? nullptr : SkRegionIter_to_ARegionIter(new SkRegion::Iterator(*region)); +} + +void ARegionIterator_releaseIterator(ARegionIterator* iterator) { + delete ARegionIter_to_SkRegionIter(iterator); +} + +bool ARegionIterator_isComplex(ARegionIterator* iterator) { + return ARegionIter_to_SkRegionIter(iterator)->rgn()->isComplex(); +} + +bool ARegionIterator_isDone(ARegionIterator* iterator) { + return ARegionIter_to_SkRegionIter(iterator)->done(); +} + +void ARegionIterator_next(ARegionIterator* iterator) { + ARegionIter_to_SkRegionIter(iterator)->next(); +} + +ARect ARegionIterator_getRect(ARegionIterator* iterator) { + const SkIRect& rect = ARegionIter_to_SkRegionIter(iterator)->rect(); + return { rect.fLeft, rect.fTop, rect.fRight, rect.fBottom }; +} + +ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator) { + const SkIRect& bounds = ARegionIter_to_SkRegionIter(iterator)->rgn()->getBounds(); + return { bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom }; +} diff --git a/core/jni/android/graphics/apex/include/android/graphics/canvas.h b/core/jni/android/graphics/apex/include/android/graphics/canvas.h new file mode 100644 index 000000000000..c35a7d69b836 --- /dev/null +++ b/core/jni/android/graphics/apex/include/android/graphics/canvas.h @@ -0,0 +1,66 @@ +/* + * 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. + */ +#ifndef ANDROID_GRAPHICS_CANVAS_H +#define ANDROID_GRAPHICS_CANVAS_H + +#include <android/native_window.h> +#include <android/rect.h> +#include <jni.h> + +__BEGIN_DECLS + +/** +* Opaque handle for a native graphics canvas. +*/ +typedef struct ACanvas ACanvas; + +// One of AHardwareBuffer_Format. +bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat); + +/** + * Returns a native handle to a Java android.graphics.Canvas + * + * @param env + * @param canvas + * @return ACanvas* that is only valid for the life of the jobject. + */ +ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvas); + +/** + * Updates the canvas to render into the pixels in the provided buffer + * + * @param canvas + * @param buffer The buffer that will provide the backing store for this canvas. The buffer must + * remain valid until the this method is called again with either another active + * buffer or nullptr. If nullptr is given the canvas will release the previous buffer + * and set an empty backing store. + * @param dataspace + */ +void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer, + int32_t /*android_dataspace_t*/ dataspace); + +/** + * Clips operations on the canvas to the intersection of the current clip and the provided clipRect. + */ +void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false); + +/** + * Clips operations on the canvas to the difference of the current clip and the provided clipRect. + */ +void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false); + +__END_DECLS +#endif // ANDROID_GRAPHICS_CANVAS_H
\ No newline at end of file diff --git a/core/jni/android/graphics/apex/include/android/graphics/region.h b/core/jni/android/graphics/apex/include/android/graphics/region.h new file mode 100644 index 000000000000..961067a8e2db --- /dev/null +++ b/core/jni/android/graphics/apex/include/android/graphics/region.h @@ -0,0 +1,76 @@ +/* + * 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. + */ +#ifndef ANDROID_GRAPHICS_REGION_H +#define ANDROID_GRAPHICS_REGION_H + +#include <android/rect.h> +#include <sys/cdefs.h> +#include <jni.h> + +__BEGIN_DECLS + +/** +* Opaque handle for a native graphics region iterator. +*/ +typedef struct ARegionIterator ARegionIterator; + +/** + * Returns a iterator for a Java android.graphics.Region + * + * @param env + * @param region + * @return ARegionIterator that must be closed and must not live longer than the life + * of the jobject. It returns nullptr if the region is not a valid object. + */ +ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject region); + +void ARegionIterator_releaseIterator(ARegionIterator* iterator); + +bool ARegionIterator_isComplex(ARegionIterator* iterator); + +bool ARegionIterator_isDone(ARegionIterator* iterator); + +void ARegionIterator_next(ARegionIterator* iterator); + +ARect ARegionIterator_getRect(ARegionIterator* iterator); + +ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator); + +__END_DECLS + +#ifdef __cplusplus +namespace android { +namespace graphics { + class RegionIterator { + public: + RegionIterator(JNIEnv* env, jobject region) + : mIterator(ARegionIterator_acquireIterator(env, region)) {} + ~RegionIterator() { ARegionIterator_releaseIterator(mIterator); } + + bool isValid() const { return mIterator != nullptr; } + bool isComplex() { return ARegionIterator_isComplex(mIterator); } + bool isDone() { return ARegionIterator_isDone(mIterator); } + void next() { ARegionIterator_next(mIterator); } + ARect getRect() { return ARegionIterator_getRect(mIterator); } + ARect getTotalBounds() const { return ARegionIterator_getTotalBounds(mIterator); } + private: + ARegionIterator* mIterator; + }; +}; // namespace graphics +}; // namespace android + +#endif // __cplusplus +#endif // ANDROID_GRAPHICS_REGION_H
\ No newline at end of file diff --git a/core/jni/android_graphics_GraphicBuffer.cpp b/core/jni/android_graphics_GraphicBuffer.cpp index bb9254c62c4d..43d22eb7df0e 100644 --- a/core/jni/android_graphics_GraphicBuffer.cpp +++ b/core/jni/android_graphics_GraphicBuffer.cpp @@ -21,11 +21,6 @@ #include <inttypes.h> #include "android_os_Parcel.h" -#include "android/graphics/GraphicsJNI.h" - -#include <android_runtime/AndroidRuntime.h> -#include <android_runtime/android_graphics_GraphicBuffer.h> - #include <binder/Parcel.h> #include <log/log.h> @@ -33,10 +28,10 @@ #include <ui/GraphicBuffer.h> #include <ui/PixelFormat.h> -#include <hwui/Bitmap.h> - -#include <SkCanvas.h> -#include <SkBitmap.h> +#include <android/native_window.h> +#include <android/graphics/canvas.h> +#include <android_runtime/android_graphics_GraphicBuffer.h> +#include <private/android/AHardwareBufferHelpers.h> #include <private/gui/ComposerService.h> @@ -146,23 +141,8 @@ static void android_graphics_GraphicBuffer_destroy(JNIEnv* env, jobject clazz, // Canvas management // ---------------------------------------------------------------------------- -static inline SkColorType convertPixelFormat(int32_t format) { - switch (format) { - case PIXEL_FORMAT_RGBA_8888: - return kN32_SkColorType; - case PIXEL_FORMAT_RGBX_8888: - return kN32_SkColorType; - case PIXEL_FORMAT_RGBA_FP16: - return kRGBA_F16_SkColorType; - case PIXEL_FORMAT_RGB_565: - return kRGB_565_SkColorType; - default: - return kUnknown_SkColorType; - } -} - static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, - jlong wrapperHandle, jobject canvas, jobject dirtyRect) { + jlong wrapperHandle, jobject canvasObj, jobject dirtyRect) { GraphicBufferWrapper* wrapper = reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle); @@ -191,24 +171,16 @@ static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, return JNI_FALSE; } - ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); - - SkBitmap bitmap; - bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(), - convertPixelFormat(buffer->getPixelFormat()), - kPremul_SkAlphaType), - bytesCount); - - if (buffer->getWidth() > 0 && buffer->getHeight() > 0) { - bitmap.setPixels(bits); - } else { - bitmap.setPixels(NULL); - } + ANativeWindow_Buffer nativeBuffer; + nativeBuffer.width = buffer->getWidth(); + nativeBuffer.height = buffer->getHeight(); + nativeBuffer.stride = buffer->getStride(); + nativeBuffer.format = AHardwareBuffer_convertFromPixelFormat(buffer->getPixelFormat()); + nativeBuffer.bits = bits; - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); - nativeCanvas->setBitmap(bitmap); - nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom, - SkClipOp::kIntersect); + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, &nativeBuffer, ADATASPACE_UNKNOWN); + ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom}); if (dirtyRect) { INVOKEV(dirtyRect, gRectClassInfo.set, @@ -219,13 +191,13 @@ static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, } static jboolean android_graphics_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobject, - jlong wrapperHandle, jobject canvas) { + jlong wrapperHandle, jobject canvasObj) { + // release the buffer from the canvas + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN); GraphicBufferWrapper* wrapper = reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle); - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); - nativeCanvas->setBitmap(SkBitmap()); - if (wrapper) { status_t status = wrapper->get()->unlock(); return status == 0 ? JNI_TRUE : JNI_FALSE; diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index 98680fa1eebe..64caf33c346a 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -22,7 +22,7 @@ #include <android_runtime/AndroidRuntime.h> #include <utils/threads.h> -#include <android/graphics/Region.h> +#include <android/graphics/region.h> #include <gui/SurfaceControl.h> #include <ui/Region.h> @@ -128,10 +128,9 @@ bool NativeInputWindowHandle::updateInfo() { jobject regionObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.touchableRegion); if (regionObj) { - SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj); - for (SkRegion::Iterator it(*region); !it.done(); it.next()) { - const SkIRect& rect = it.rect(); - mInfo.addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom)); + for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) { + ARect rect = it.getRect(); + mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom)); } env->DeleteLocalRef(regionObj); } diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp index 7e3b083607e0..8553a2c24826 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -210,8 +210,7 @@ static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject if (parcel) { bool isInitialized = parcel->readInt32(); if (isInitialized) { - InputChannel* inputChannel = new InputChannel(); - inputChannel->read(*parcel); + sp<InputChannel> inputChannel = InputChannel::read(*parcel); NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel); diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index d65e2522a83c..4c2e91f986d0 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -20,15 +20,16 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> -#include "android_os_Parcel.h" -#include "android/graphics/GraphicsJNI.h" - #include "core_jni_helpers.h" + +#include <android/graphics/canvas.h> #include <android_runtime/android_graphics_GraphicBuffer.h> #include <android_runtime/android_graphics_SurfaceTexture.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/Log.h> +#include <private/android/AHardwareBufferHelpers.h> +#include "android_os_Parcel.h" #include <binder/Parcel.h> #include <gui/Surface.h> @@ -39,11 +40,6 @@ #include <ui/Rect.h> #include <ui/Region.h> -#include <SkCanvas.h> -#include <SkBitmap.h> -#include <SkImage.h> -#include <SkRegion.h> - #include <utils/misc.h> #include <utils/Log.h> @@ -232,37 +228,21 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, dirtyRectPtr = &dirtyRect; } - ANativeWindow_Buffer outBuffer; - status_t err = surface->lock(&outBuffer, dirtyRectPtr); + ANativeWindow_Buffer buffer; + status_t err = surface->lock(&buffer, dirtyRectPtr); if (err < 0) { const char* const exception = (err == NO_MEMORY) ? - OutOfResourcesException : - "java/lang/IllegalArgumentException"; + OutOfResourcesException : IllegalArgumentException; jniThrowException(env, exception, NULL); return 0; } - SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height, - convertPixelFormat(outBuffer.format), - outBuffer.format == PIXEL_FORMAT_RGBX_8888 - ? kOpaque_SkAlphaType : kPremul_SkAlphaType); - - SkBitmap bitmap; - ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); - bitmap.setInfo(info, bpr); - if (outBuffer.width > 0 && outBuffer.height > 0) { - bitmap.setPixels(outBuffer.bits); - } else { - // be safe with an empty bitmap. - bitmap.setPixels(NULL); - } - - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); - nativeCanvas->setBitmap(bitmap); + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, &buffer, static_cast<int32_t>(surface->getBuffersDataSpace())); if (dirtyRectPtr) { - nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top, - dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect); + ACanvas_clipRect(canvas, {dirtyRect.left, dirtyRect.top, + dirtyRect.right, dirtyRect.bottom}); } if (dirtyRectObj) { @@ -288,8 +268,8 @@ static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, } // detach the canvas from the surface - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); - nativeCanvas->setBitmap(SkBitmap()); + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN); // unlock surface status_t err = surface->unlockAndPost(); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 096bf6977a9e..67f52f464efa 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -20,9 +20,9 @@ #include "android_os_Parcel.h" #include "android_util_Binder.h" #include "android_hardware_input_InputWindowHandle.h" -#include "android/graphics/Region.h" #include "core_jni_helpers.h" +#include <android/graphics/region.h> #include <android_runtime/AndroidRuntime.h> #include <android-base/chrono_utils.h> #include <nativehelper/JNIHelp.h> @@ -425,20 +425,19 @@ static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj, static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobject regionObj) { SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); - SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj); - if (!region) { + graphics::RegionIterator iterator(env, regionObj); + if (!iterator.isValid()) { doThrowIAE(env); return; } - const SkIRect& b(region->getBounds()); - Region reg(Rect(b.fLeft, b.fTop, b.fRight, b.fBottom)); - if (region->isComplex()) { - SkRegion::Iterator it(*region); - while (!it.done()) { - const SkIRect& r(it.rect()); - reg.addRectUnchecked(r.fLeft, r.fTop, r.fRight, r.fBottom); - it.next(); + ARect bounds = iterator.getTotalBounds(); + Region reg({bounds.left, bounds.top, bounds.right, bounds.bottom}); + if (iterator.isComplex()) { + while (!iterator.isDone()) { + ARect rect = iterator.getRect(); + reg.addRectUnchecked(rect.left, rect.top, rect.right, rect.bottom); + iterator.next(); } } diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 15319adf134a..1f69c8bbbe5d 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -25,11 +25,7 @@ #include <gui/GLConsumer.h> #include <gui/Surface.h> -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <SkImage.h> - -#include "android/graphics/GraphicsJNI.h" +#include <android/graphics/canvas.h> #include "core_jni_helpers.h" @@ -70,33 +66,6 @@ static struct { // Native layer // ---------------------------------------------------------------------------- -// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp) -static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) { - SkColorType colorType = kUnknown_SkColorType; - SkAlphaType alphaType = kOpaque_SkAlphaType; - switch (buffer.format) { - case WINDOW_FORMAT_RGBA_8888: - colorType = kN32_SkColorType; - alphaType = kPremul_SkAlphaType; - break; - case WINDOW_FORMAT_RGBX_8888: - colorType = kN32_SkColorType; - alphaType = kOpaque_SkAlphaType; - break; - case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: - colorType = kRGBA_F16_SkColorType; - alphaType = kPremul_SkAlphaType; - break; - case WINDOW_FORMAT_RGB_565: - colorType = kRGB_565_SkColorType; - alphaType = kOpaque_SkAlphaType; - break; - default: - break; - } - return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType); -} - /** * This is a private API, and this implementation is also provided in the NDK. * However, the NDK links against android_runtime, which means that using the @@ -134,14 +103,12 @@ static void android_view_TextureView_destroyNativeWindow(JNIEnv* env, jobject te } static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, - jlong nativeWindow, jobject canvas, jobject dirtyRect) { + jlong nativeWindow, jobject canvasObj, jobject dirtyRect) { if (!nativeWindow) { return JNI_FALSE; } - ANativeWindow_Buffer buffer; - Rect rect(Rect::EMPTY_RECT); if (dirtyRect) { rect.left = GET_INT(dirtyRect, gRectClassInfo.left); @@ -152,25 +119,14 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, rect.set(Rect(0x3FFF, 0x3FFF)); } + ANativeWindow_Buffer outBuffer; sp<ANativeWindow> window((ANativeWindow*) nativeWindow); - int32_t status = native_window_lock(window.get(), &buffer, &rect); + int32_t status = native_window_lock(window.get(), &outBuffer, &rect); if (status) return JNI_FALSE; - ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format); - - SkBitmap bitmap; - bitmap.setInfo(convertPixelFormat(buffer), bytesCount); - - if (buffer.width > 0 && buffer.height > 0) { - bitmap.setPixels(buffer.bits); - } else { - bitmap.setPixels(NULL); - } - - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); - nativeCanvas->setBitmap(bitmap); - nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom, - SkClipOp::kIntersect); + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, &outBuffer, ANativeWindow_getBuffersDataSpace(window.get())); + ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom}); if (dirtyRect) { INVOKEV(dirtyRect, gRectClassInfo.set, @@ -181,10 +137,11 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, } static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject, - jlong nativeWindow, jobject canvas) { + jlong nativeWindow, jobject canvasObj) { - Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); - nativeCanvas->setBitmap(SkBitmap()); + // release the buffer from the canvas + ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj); + ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN); if (nativeWindow) { sp<ANativeWindow> window((ANativeWindow*) nativeWindow); diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index ea4b25267512..bb5780558bdf 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -80,9 +80,9 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } - // Jars from the runtime apex are allowed. - static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/"; - if (android::base::StartsWith(path, kRuntimeApexPrefix) + // Jars from the ART APEX are allowed. + static const char* kArtApexPrefix = "/apex/com.android.art/javalib/"; + if (android::base::StartsWith(path, kArtApexPrefix) && android::base::EndsWith(path, kJarSuffix)) { return true; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index a1ff9b94123f..d54b6b0b00e5 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -367,10 +367,8 @@ message MemInfoDumpProto { optional int32 id = 3; optional bool is_proc = 4; optional bool has_activities = 5; - oneof ss_kb { - int64 pss_kb = 6; - int64 rss_kb = 9; - } + optional int64 pss_kb = 6; + optional int64 rss_kb = 9; optional int64 swap_pss_kb = 7; repeated MemItem sub_items = 8; } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index aac144c4d732..79167ab476c1 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -308,6 +308,8 @@ message ConstantsProto { // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't // ready now. reserved 1; // skip_not_ready_jobs + // Whether or not TimeController will use a non-wakeup alarm for delay constraints. + optional bool use_non_wakeup_alarm_for_delay = 2; } optional TimeController time_controller = 25; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6a20484f6faa..9738759c5f3e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1264,6 +1264,15 @@ android:description="@string/permdesc_camera" android:protectionLevel="dangerous|instant" /> + <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access + system only camera devices. + <p>Protection level: system|signature + @hide --> + <permission android:name="android.permission.SYSTEM_CAMERA" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_systemCamera" + android:description="@string/permdesc_systemCamera" + android:protectionLevel="system|signature" /> <!-- ====================================================================== --> <!-- Permissions for accessing the device sensors --> @@ -4458,12 +4467,12 @@ <!-- @SystemApi Allows to access all app shortcuts. @hide --> <permission android:name="android.permission.ACCESS_SHORTCUTS" - android:protectionLevel="signature|textClassifier" /> + android:protectionLevel="signature|appPredictor" /> <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs. @hide --> <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS" - android:protectionLevel="signature|textClassifier" /> + android:protectionLevel="signature|appPredictor" /> <!-- @SystemApi Allows an application to read the runtime profiles of other apps. @hide <p>Not for use by third-party applications. --> @@ -4524,6 +4533,13 @@ <permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to + ensure that only the system can bind to it. + @hide This is not a third-party API (intended for OEMs and system apps). + --> + <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE" + android:protectionLevel="signature" /> + <!-- @hide Permission that allows configuring appops. <p>Not for use by third-party applications. --> <permission android:name="android.permission.MANAGE_APPOPS" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ceccd0dcdfec..ab8a2b0e3fee 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1703,16 +1703,16 @@ <!-- Add packages here --> </string-array> - <!-- The set of system packages on device that are queryable regardless of the contents of their - manifest. --> + <!-- The set of system packages on device that are queryable by any app regardless of the + contents of its manifest. --> <string-array name="config_forceQueryablePackages" translatable="false"> <item>com.android.settings</item> <item>com.android.providers.settings</item> <!-- Add packages here --> </string-array> - <!-- If true, will force all packages on any system partition as queryable regardless of the - contents of their manifest. --> + <!-- If true, will force all packages on any system partition as queryable by any app regardless + of the contents of its manifest. --> <bool name="config_forceSystemPackagesQueryable">false</bool> <!-- Component name of the default wallpaper. This will be ImageWallpaper if not diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2067735f2620..96c0cf3fd86a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2927,7 +2927,6 @@ <public type="attr" name="enforceStatusBarContrast" id="0x01010604" /> <public type="attr" name="enforceNavigationBarContrast" id="0x01010605" /> <public type="attr" name="identifier" id="0x01010606" /> - <public type="attr" name="forceQueryable" id="0x01010608" /> <!-- @hide @SystemApi --> <public type="drawable" name="ic_info" id="0x010800b4" /> @@ -3000,6 +2999,7 @@ <public-group type="attr" first-id="0x01010607"> <public name="importantForContentCapture" /> + <public name="forceQueryable" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index cdfa0439c7ca..2b97bf87304f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1173,6 +1173,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permdesc_systemCamera">This privileged | system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_vibrate">control vibration</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java new file mode 100644 index 000000000000..f1bebfb9bd93 --- /dev/null +++ b/core/tests/coretests/src/android/util/ArraySetTest.java @@ -0,0 +1,113 @@ +/* + * 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; + +import androidx.test.filters.LargeTest; + +import junit.framework.TestCase; + +import org.junit.After; +import org.junit.Test; + +import java.util.ConcurrentModificationException; + +/** + * Unit tests for ArraySet that don't belong in CTS. + */ +@LargeTest +public class ArraySetTest extends TestCase { + private static final String TAG = "ArraySetTest"; + ArraySet<String> mSet = new ArraySet<>(); + + @After + public void tearDown() { + mSet = null; + } + + /** + * Attempt to generate a ConcurrentModificationException in ArraySet. + * <p> + * ArraySet is explicitly documented to be non-thread-safe, yet it's easy to accidentally screw + * this up; ArraySet should (in the spirit of the core Java collection types) make an effort to + * catch this and throw ConcurrentModificationException instead of crashing somewhere in its + * internals. + */ + @Test + public void testConcurrentModificationException() throws Exception { + final int testDurMs = 10_000; + System.out.println("Starting ArraySet concurrency test"); + new Thread(() -> { + int i = 0; + while (mSet != null) { + try { + mSet.add(String.format("key %d", i++)); + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e); + fail("Concurrent modification uncaught, causing indexing failure: " + e); + } catch (ClassCastException e) { + Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e); + fail("Concurrent modification uncaught, causing cache corruption: " + e); + } catch (ConcurrentModificationException e) { + System.out.println("[successfully caught CME at put #" + i + + " size=" + (mSet == null ? "??" : String.valueOf(mSet.size())) + "]"); + if (i % 200 == 0) { + System.out.print("."); + } + } + } + }).start(); + for (int i = 0; i < (testDurMs / 100); i++) { + try { + if (mSet.size() % 4 == 0) { + mSet.clear(); + } + System.out.print("X"); + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e); + fail("Concurrent modification uncaught, causing indexing failure: " + e); + } catch (ClassCastException e) { + Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e); + fail("Concurrent modification uncaught, causing cache corruption: " + e); + } catch (ConcurrentModificationException e) { + System.out.println( + "[successfully caught CME at clear #" + i + " size=" + mSet.size() + "]"); + } + } + } + + /** + * Check to make sure the same operations behave as expected in a single thread. + */ + @Test + public void testNonConcurrentAccesses() throws Exception { + for (int i = 0; i < 100000; i++) { + try { + mSet.add(String.format("key %d", i++)); + if (i % 200 == 0) { + System.out.print("."); + } + if (i % 500 == 0) { + mSet.clear(); + System.out.print("X"); + } + } catch (ConcurrentModificationException e) { + Log.e(TAG, "concurrent modification caught on single thread", e); + fail(); + } + } + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 4187c806c66a..89523d6bbefb 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -322,6 +322,8 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <!-- Permission required to test system only camera devices. --> + <permission name="android.permission.SYSTEM_CAMERA" /> <!-- Permission required to test ExplicitHealthCheckServiceImpl. --> <permission name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"/> </privapp-permissions> diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 9a27f28154e0..b93759f87da2 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -21,6 +21,7 @@ #ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows #include <android/hardware_buffer.h> +#include <android/native_window.h> #endif #include <algorithm> @@ -30,11 +31,11 @@ namespace android { namespace uirenderer { #ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows -SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, - sk_sp<SkColorSpace> colorSpace) { +static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t format, + sk_sp<SkColorSpace> colorSpace) { SkColorType colorType = kUnknown_SkColorType; SkAlphaType alphaType = kOpaque_SkAlphaType; - switch (bufferDesc.format) { + switch (format) { case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: colorType = kN32_SkColorType; alphaType = kPremul_SkAlphaType; @@ -56,10 +57,20 @@ SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, alphaType = kPremul_SkAlphaType; break; default: - ALOGV("Unsupported format: %d, return unknown by default", bufferDesc.format); + ALOGV("Unsupported format: %d, return unknown by default", format); break; } - return SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType, alphaType, colorSpace); + return SkImageInfo::Make(width, height, colorType, alphaType, colorSpace); +} + +SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer, + sk_sp<SkColorSpace> colorSpace) { + return createImageInfo(buffer.width, buffer.height, buffer.format, colorSpace); +} + +SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, + sk_sp<SkColorSpace> colorSpace) { + return createImageInfo(bufferDesc.width, bufferDesc.height, bufferDesc.format, colorSpace); } #endif diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 7c2378a5aeb3..07b5ec8fe0f0 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -25,6 +25,7 @@ #include <SkColorSpace.h> #include <SkImageInfo.h> +struct ANativeWindow_Buffer; struct AHardwareBuffer_Desc; namespace android { @@ -91,8 +92,13 @@ static constexpr float EOCF_sRGB(float srgb) { return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); } +#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows +ANDROID_API SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer, + sk_sp<SkColorSpace> colorSpace); + SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, sk_sp<SkColorSpace> colorSpace); +#endif android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format); diff --git a/location/lib/Android.bp b/location/lib/Android.bp index db63889d7743..b15cc5ccd656 100644 --- a/location/lib/Android.bp +++ b/location/lib/Android.bp @@ -24,6 +24,5 @@ java_sdk_library { srcs_lib: "framework-minus-apex", // TODO(b/70046217): remove core/java and android below. It was added to provide definitions for // types like android.os.Bundle - srcs_lib_whitelist_dirs: ["core/java", "location/java"], srcs_lib_whitelist_pkgs: ["android", "com.android.internal.location"], } diff --git a/media/Android.bp b/media/Android.bp index 29064adf8a01..ef322397deb3 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -67,6 +67,7 @@ filegroup { "apex/java/android/media/Session2Link.java", "apex/java/android/media/Session2Token.java", ], + path: "apex/java", } filegroup { @@ -79,6 +80,7 @@ filegroup { "apex/java/android/media/IMediaSession2.aidl", "apex/java/android/media/IMediaSession2Service.aidl", ], + path: "apex/java", } filegroup { @@ -98,6 +100,7 @@ filegroup { "apex/java/android/media/BufferingParams.java", "apex/java/android/media/ProxyDataSourceCallback.java", ], + path: "apex/java", } metalava_updatable_media_args = " --error UnhiddenSystemApi " + diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index 8d08beb4a72c..6f44d45c0a12 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -22,5 +22,5 @@ import android.media.MediaRoute2ProviderInfo; * @hide */ oneway interface IMediaRoute2ProviderClient { - void notifyProviderInfoUpdated(in MediaRoute2ProviderInfo info); + void updateProviderInfo(in MediaRoute2ProviderInfo info); } diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl index 774d6a764f32..26184af699db 100644 --- a/media/java/android/media/IMediaRouter2Client.aidl +++ b/media/java/android/media/IMediaRouter2Client.aidl @@ -16,10 +16,12 @@ package android.media; +import android.media.MediaRoute2ProviderInfo; + /** * @hide */ oneway interface IMediaRouter2Client { - void notifyStateChanged(); void notifyRestoreRoute(); + void notifyProviderInfosUpdated(in List<MediaRoute2ProviderInfo> providers); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 08266a5a2051..1b713b6ab74b 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -42,7 +42,7 @@ interface IMediaRouterService { void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); // Methods for media router 2 - void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId); + void registerClient2(IMediaRouter2Client client, String packageName); void unregisterClient2(IMediaRouter2Client client); void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request); /** @@ -54,8 +54,7 @@ interface IMediaRouterService { void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route); void setControlCategories(IMediaRouter2Client client, in List<String> categories); - void registerManagerAsUser(IMediaRouter2Manager manager, - String packageName, int userId); + void registerManager(IMediaRouter2Manager manager, String packageName); void unregisterManager(IMediaRouter2Manager manager); /** * Changes the selected route of an application. diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index b89b0b1a5f02..e8e0f826e6b6 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -98,7 +98,7 @@ public abstract class MediaRoute2ProviderService extends Service { return; } try { - mClient.notifyProviderInfoUpdated(mProviderInfo); + mClient.updateProviderInfo(mProviderInfo); } catch (RemoteException ex) { Log.w(TAG, "Failed to send onProviderInfoUpdated"); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index f4dffa28f133..8e29e34caa14 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -16,13 +16,16 @@ package android.media; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.content.Context; import android.content.Intent; +import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -58,6 +61,12 @@ public class MediaRouter2 { private Client mClient; private final String mPackageName; + final Handler mHandler; + + List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList(); + volatile List<MediaRoute2Info> mRoutes = Collections.emptyList(); + + MediaRoute2Info mSelectedRoute; /** * Gets an instance of the media router associated with the context. @@ -78,6 +87,7 @@ public class MediaRouter2 { ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); mPackageName = mContext.getPackageName(); //TODO: read control categories from the manifest + mHandler = new Handler(Looper.getMainLooper()); } /** @@ -100,25 +110,10 @@ public class MediaRouter2 { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(callback, "callback must not be null"); + CallbackRecord record; // This is required to prevent adding the same callback twice. synchronized (mCallbackRecords) { - if (mCallbackRecords.size() == 0) { - synchronized (sLock) { - Client client = new Client(); - try { - mMediaRouterService.registerClient2AsUser(client, mPackageName, - UserHandle.myUserId()); - //TODO: We should merge control categories of callbacks. - mMediaRouterService.setControlCategories(client, mControlCategories); - mClient = client; - } catch (RemoteException ex) { - Log.e(TAG, "Unable to register media router.", ex); - } - } - } - final int index = findCallbackRecordIndexLocked(callback); - CallbackRecord record; if (index < 0) { record = new CallbackRecord(callback); mCallbackRecords.add(record); @@ -129,6 +124,20 @@ public class MediaRouter2 { record.mFlags = flags; } + synchronized (sLock) { + if (mClient == null) { + Client client = new Client(); + try { + mMediaRouterService.registerClient2(client, mPackageName); + mMediaRouterService.setControlCategories(client, mControlCategories); + mClient = client; + } catch (RemoteException ex) { + Log.e(TAG, "Unable to register media router.", ex); + } + } + } + record.notifyRoutes(); + //TODO: Update discovery request here. } @@ -172,10 +181,9 @@ public class MediaRouter2 { Objects.requireNonNull(controlCategories, "control categories must not be null"); Client client; - List<String> newControlCategories; + List<String> newControlCategories = new ArrayList<>(controlCategories); synchronized (sLock) { - mControlCategories = new ArrayList<>(controlCategories); - newControlCategories = mControlCategories; + mControlCategories = newControlCategories; client = mClient; } if (client != null) { @@ -185,8 +193,29 @@ public class MediaRouter2 { Log.e(TAG, "Unable to set control categories.", ex); } } + mHandler.sendMessage(obtainMessage(MediaRouter2::refreshAndNotifyRoutes, this)); } + /** + * Gets the list of {@link MediaRoute2Info routes} currently known to the media router. + * + * @return the list of routes that support at least one of the control categories set by + * the application + */ + @NonNull + public List<MediaRoute2Info> getRoutes() { + return mRoutes; + } + + /** + * Gets the currently selected route. + * + * @return the selected route + */ + @NonNull + public MediaRoute2Info getSelectedRoute() { + return mSelectedRoute; + } /** * Selects the specified route. @@ -199,6 +228,7 @@ public class MediaRouter2 { Client client; synchronized (sLock) { + mSelectedRoute = route; client = mClient; } if (client != null) { @@ -247,6 +277,61 @@ public class MediaRouter2 { return -1; } + void onProviderInfosUpdated(List<MediaRoute2ProviderInfo> providers) { + if (providers == null) { + Log.w(TAG, "Providers info is null."); + return; + } + + mProviders = providers; + refreshAndNotifyRoutes(); + } + + void refreshAndNotifyRoutes() { + ArrayList<MediaRoute2Info> routes = new ArrayList<>(); + + List<String> controlCategories; + synchronized (sLock) { + controlCategories = mControlCategories; + } + + for (MediaRoute2ProviderInfo provider : mProviders) { + updateProvider(provider, controlCategories, routes); + } + + //TODO: Can orders be changed? + if (!Objects.equals(mRoutes, routes)) { + mRoutes = Collections.unmodifiableList(routes); + notifyRouteListChanged(mRoutes); + } + } + + void updateProvider(MediaRoute2ProviderInfo provider, List<String> controlCategories, + List<MediaRoute2Info> outRoutes) { + if (provider == null || !provider.isValid()) { + Log.w(TAG, "Ignoring invalid provider : " + provider); + } + + final Collection<MediaRoute2Info> routes = provider.getRoutes(); + for (MediaRoute2Info route : routes) { + if (!route.isValid()) { + Log.w(TAG, "Ignoring invalid route : " + route); + continue; + } + if (!route.supportsControlCategory(controlCategories)) { + continue; + } + outRoutes.add(route); + } + } + + void notifyRouteListChanged(List<MediaRoute2Info> routes) { + for (CallbackRecord record: mCallbackRecords) { + record.mExecutor.execute( + () -> record.mCallback.onRoutesChanged(routes)); + } + } + /** * Interface for receiving events about media routing changes. */ @@ -265,9 +350,14 @@ public class MediaRouter2 { * Called when a route is removed. */ public void onRouteRemoved(MediaRoute2Info routeInfo) {} + + /** + * Called when the list of routes is changed. + */ + public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} } - static final class CallbackRecord { + final class CallbackRecord { public final Callback mCallback; public Executor mExecutor; public int mFlags; @@ -275,13 +365,25 @@ public class MediaRouter2 { CallbackRecord(@NonNull Callback callback) { mCallback = callback; } + + void notifyRoutes() { + final List<MediaRoute2Info> routes = mRoutes; + // notify only when bound to media router service. + //TODO: Correct the condition when control category, default rotue, .. are finalized. + if (routes.size() > 0) { + mExecutor.execute(() -> mCallback.onRoutesChanged(routes)); + } + } } class Client extends IMediaRouter2Client.Stub { @Override - public void notifyStateChanged() throws RemoteException {} + public void notifyRestoreRoute() throws RemoteException {} @Override - public void notifyRestoreRoute() throws RemoteException {} + public void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> info) { + mHandler.sendMessage(obtainMessage(MediaRouter2::onProviderInfosUpdated, + MediaRouter2.this, info)); + } } } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 6c53f7d6d73f..0b645691ea3b 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -25,7 +25,6 @@ import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -104,26 +103,28 @@ public class MediaRouter2Manager { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(callback, "callback must not be null"); + CallbackRecord callbackRecord; synchronized (mCallbacks) { if (findCallbackRecordIndexLocked(callback) >= 0) { Log.w(TAG, "Ignoring to add the same callback twice."); return; } - synchronized (sLock) { - if (mCallbacks.size() == 0) { - Client client = new Client(); - try { - mMediaRouterService.registerManagerAsUser(client, mPackageName, - UserHandle.myUserId()); - mClient = client; - } catch (RemoteException ex) { - Log.e(TAG, "Unable to register media router manager.", ex); - } + callbackRecord = new CallbackRecord(executor, callback); + mCallbacks.add(callbackRecord); + } + + synchronized (sLock) { + if (mClient == null) { + Client client = new Client(); + try { + mMediaRouterService.registerManager(client, mPackageName); + mClient = client; + } catch (RemoteException ex) { + Log.e(TAG, "Unable to register media router manager.", ex); } + } else { + callbackRecord.notifyRoutes(); } - CallbackRecord record = new CallbackRecord(executor, callback); - mCallbacks.add(record); - record.notifyRoutes(); } } @@ -149,6 +150,7 @@ public class MediaRouter2Manager { } catch (RemoteException ex) { Log.e(TAG, "Unable to unregister media router manager", ex); } + mClient.notifyProviderInfosUpdated(Collections.emptyList()); mClient = null; } } @@ -255,6 +257,10 @@ public class MediaRouter2Manager { final MediaRoute2ProviderInfo prevProvider = mProviders.get(index); final Set<String> updatedRouteIds = new HashSet<>(); for (MediaRoute2Info routeInfo : routes) { + if (!routeInfo.isValid()) { + Log.w(TAG, "Ignoring invalid route : " + routeInfo); + continue; + } final MediaRoute2Info prevRoute = prevProvider.getRoute(routeInfo.getId()); if (prevRoute == null) { notifyRouteAdded(routeInfo); @@ -303,7 +309,7 @@ public class MediaRouter2Manager { void notifyRouteListChanged() { for (CallbackRecord record: mCallbacks) { record.mExecutor.execute( - () -> record.mCallback.onRouteListChanged(mRoutes)); + () -> record.mCallback.onRoutesChanged(mRoutes)); } } @@ -369,10 +375,10 @@ public class MediaRouter2Manager { public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} /** - * Called when the list of routes are changed. + * Called when the list of routes is changed. * A client may refresh available routes for each application. */ - public void onRouteListChanged(@NonNull List<MediaRoute2Info> routes) {} + public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} } final class CallbackRecord { @@ -385,6 +391,7 @@ public class MediaRouter2Manager { } void notifyRoutes() { + mExecutor.execute(() -> mCallback.onRoutesChanged(mRoutes)); for (MediaRoute2Info routeInfo : mRoutes) { mExecutor.execute( () -> mCallback.onRouteAdded(routeInfo)); diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 471fa2c4bad9..7eec8d9f6cc3 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -16,17 +16,20 @@ package android.media; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; +import android.content.ContentProviderClient; import android.content.Context; -import android.content.Intent; import android.content.ServiceConnection; -import android.media.IMediaScannerListener; -import android.media.IMediaScannerService; import android.net.Uri; +import android.os.Build; import android.os.IBinder; -import android.os.RemoteException; +import android.provider.MediaStore; import android.util.Log; +import com.android.internal.os.BackgroundThread; + +import java.io.File; /** * MediaScannerConnection provides a way for applications to pass a @@ -38,20 +41,24 @@ import android.util.Log; * to the client of the MediaScannerConnection class. */ public class MediaScannerConnection implements ServiceConnection { - private static final String TAG = "MediaScannerConnection"; - private Context mContext; - private MediaScannerConnectionClient mClient; - private IMediaScannerService mService; - private boolean mConnected; // true if connect() has been called since last disconnect() + private final Context mContext; + private final MediaScannerConnectionClient mClient; + private ContentProviderClient mProvider; + + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) + private IMediaScannerService mService; + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) + private boolean mConnected; + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { + @Override public void scanCompleted(String path, Uri uri) { - MediaScannerConnectionClient client = mClient; - if (client != null) { - client.onScanCompleted(path, uri); - } } }; @@ -81,15 +88,6 @@ public class MediaScannerConnection implements ServiceConnection { * MediaScanner service has been established. */ public void onMediaScannerConnected(); - - /** - * Called to notify the client when the media scanner has finished - * scanning a file. - * @param path the path to the file that has been scanned. - * @param uri the Uri for the file if the scanning operation succeeded - * and the file was added to the media database, or null if scanning failed. - */ - public void onScanCompleted(String path, Uri uri); } /** @@ -111,13 +109,12 @@ public class MediaScannerConnection implements ServiceConnection { */ public void connect() { synchronized (this) { - if (!mConnected) { - Intent intent = new Intent(IMediaScannerService.class.getName()); - intent.setComponent( - new ComponentName("com.android.providers.media", - "com.android.providers.media.MediaScannerService")); - mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); - mConnected = true; + if (mProvider == null) { + mProvider = mContext.getContentResolver() + .acquireContentProviderClient(MediaStore.AUTHORITY); + if (mClient != null) { + mClient.onMediaScannerConnected(); + } } } } @@ -127,22 +124,9 @@ public class MediaScannerConnection implements ServiceConnection { */ public void disconnect() { synchronized (this) { - if (mConnected) { - if (false) { - Log.v(TAG, "Disconnecting from Media Scanner"); - } - try { - mContext.unbindService(this); - if (mClient instanceof ClientProxy) { - mClient = null; - } - mService = null; - } catch (IllegalArgumentException ex) { - if (false) { - Log.v(TAG, "disconnect failed: " + ex); - } - } - mConnected = false; + if (mProvider != null) { + mProvider.close(); + mProvider = null; } } } @@ -152,7 +136,7 @@ public class MediaScannerConnection implements ServiceConnection { * @return true if we are connected, false otherwise */ public synchronized boolean isConnected() { - return (mService != null && mConnected); + return (mProvider != null); } /** @@ -166,107 +150,107 @@ public class MediaScannerConnection implements ServiceConnection { */ public void scanFile(String path, String mimeType) { synchronized (this) { - if (mService == null || !mConnected) { + if (mProvider == null) { throw new IllegalStateException("not connected to MediaScannerService"); } - try { - if (false) { - Log.v(TAG, "Scanning file " + path); + BackgroundThread.getExecutor().execute(() -> { + final Uri uri = scanFileQuietly(mProvider, new File(path)); + if (mClient != null) { + mClient.onScanCompleted(path, uri); } - mService.requestScanFile(path, mimeType, mListener); - } catch (RemoteException e) { - if (false) { - Log.d(TAG, "Failed to scan file " + path); + }); + } + } + + /** + * Convenience for constructing a {@link MediaScannerConnection}, calling + * {@link #connect} on it, and calling {@link #scanFile} with the given + * <var>path</var> and <var>mimeType</var> when the connection is + * established. + * @param context The caller's Context, required for establishing a connection to + * the media scanner service. + * Success or failure of the scanning operation cannot be determined until + * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. + * @param paths Array of paths to be scanned. + * @param mimeTypes Optional array of MIME types for each path. + * If mimeType is null, then the mimeType will be inferred from the file extension. + * @param callback Optional callback through which you can receive the + * scanned URI and MIME type; If null, the file will be scanned but + * you will not get a result back. + * @see #scanFile(String, String) + */ + public static void scanFile(Context context, String[] paths, String[] mimeTypes, + OnScanCompletedListener callback) { + BackgroundThread.getExecutor().execute(() -> { + try (ContentProviderClient client = context.getContentResolver() + .acquireContentProviderClient(MediaStore.AUTHORITY)) { + for (String path : paths) { + final Uri uri = scanFileQuietly(client, new File(path)); + if (callback != null) { + callback.onScanCompleted(path, uri); + } } } + }); + } + + private static Uri scanFileQuietly(ContentProviderClient client, File file) { + Uri uri = null; + try { + uri = MediaStore.scanFile(client, file); + Log.d(TAG, "Scanned " + file + " to " + uri); + } catch (Exception e) { + Log.w(TAG, "Failed to scan " + file + ": " + e); } + return uri; } + @Deprecated static class ClientProxy implements MediaScannerConnectionClient { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mPaths; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mMimeTypes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final OnScanCompletedListener mClient; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) MediaScannerConnection mConnection; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) int mNextPath; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) { mPaths = paths; mMimeTypes = mimeTypes; mClient = client; } + @Override public void onMediaScannerConnected() { - scanNextPath(); } + @Override public void onScanCompleted(String path, Uri uri) { - if (mClient != null) { - mClient.onScanCompleted(path, uri); - } - scanNextPath(); } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) void scanNextPath() { - if (mNextPath >= mPaths.length) { - mConnection.disconnect(); - mConnection = null; - return; - } - String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; - mConnection.scanFile(mPaths[mNextPath], mimeType); - mNextPath++; } } /** - * Convenience for constructing a {@link MediaScannerConnection}, calling - * {@link #connect} on it, and calling {@link #scanFile} with the given - * <var>path</var> and <var>mimeType</var> when the connection is - * established. - * @param context The caller's Context, required for establishing a connection to - * the media scanner service. - * Success or failure of the scanning operation cannot be determined until - * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. - * @param paths Array of paths to be scanned. - * @param mimeTypes Optional array of MIME types for each path. - * If mimeType is null, then the mimeType will be inferred from the file extension. - * @param callback Optional callback through which you can receive the - * scanned URI and MIME type; If null, the file will be scanned but - * you will not get a result back. - * @see #scanFile(String, String) - */ - public static void scanFile(Context context, String[] paths, String[] mimeTypes, - OnScanCompletedListener callback) { - ClientProxy client = new ClientProxy(paths, mimeTypes, callback); - MediaScannerConnection connection = new MediaScannerConnection(context, client); - client.mConnection = connection; - connection.connect(); - } - - /** * Part of the ServiceConnection interface. Do not call. */ + @Override public void onServiceConnected(ComponentName className, IBinder service) { - if (false) { - Log.v(TAG, "Connected to Media Scanner"); - } - synchronized (this) { - mService = IMediaScannerService.Stub.asInterface(service); - if (mService != null && mClient != null) { - mClient.onMediaScannerConnected(); - } - } + // No longer needed } /** * Part of the ServiceConnection interface. Do not call. */ + @Override public void onServiceDisconnected(ComponentName className) { - if (false) { - Log.v(TAG, "Disconnected from Media Scanner"); - } - synchronized (this) { - mService = null; - } + // No longer needed } } diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java index be0d966fa075..792a2ba678fd 100644 --- a/media/java/android/media/Metadata.java +++ b/media/java/android/media/Metadata.java @@ -272,15 +272,6 @@ import java.util.TimeZone; @UnsupportedAppUsage public Metadata() { } - // Have to declare protected for finalize() since it is protected - // in the base class Object. - @Override - protected void finalize() throws Throwable { - if (mParcel != null) { - mParcel.recycle(); - } - } - /** * Go over all the records, collecting metadata keys and records' * type field offset in the Parcel. These are stored in @@ -427,10 +418,6 @@ import java.util.TimeZone; parcel.setDataPosition(pin); return false; } - - if (mParcel != null) { - mParcel.recycle(); - } mParcel = parcel; return true; } diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp index f320397bc84b..338ec12a9e28 100644 --- a/media/lib/signer/Android.bp +++ b/media/lib/signer/Android.bp @@ -19,6 +19,5 @@ java_sdk_library { srcs: ["java/**/*.java"], api_packages: ["com.android.mediadrm.signer"], srcs_lib: "framework-minus-apex", - srcs_lib_whitelist_dirs: ["media/java"], srcs_lib_whitelist_pkgs: ["android.media"], } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 946fb5e0e0ec..3abf0a4941e6 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -18,6 +18,7 @@ package com.android.mediaroutertest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; @@ -42,10 +43,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest @@ -96,6 +95,7 @@ public class MediaRouterManagerTest { mPackageName = mContext.getPackageName(); } + //TODO: Move to a seperate file @Test public void testMediaRoute2Info() { MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name") @@ -159,7 +159,7 @@ public class MediaRouterManagerTest { mRouter.unregisterCallback(mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)) - .onRouteListChanged(argThat(routes -> routes.size() > 0)); + .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mManager.getAvailableRoutes(mPackageName)); @@ -170,40 +170,48 @@ public class MediaRouterManagerTest { mManager.unregisterCallback(mockCallback); } + /** + * Tests if we get proper routes for application that has special control category. + */ @Test - public void onRouteSelectedTest() throws Exception { - CountDownLatch latch = new CountDownLatch(1); + public void testGetRoutes() throws Exception { + MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); + mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); + mRouter.registerCallback(mExecutor, mockCallback); + verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) + .onRoutesChanged(argThat(routes -> routes.size() > 0)); + Map<String, MediaRoute2Info> routes = createRouteMap(mRouter.getRoutes()); + Assert.assertEquals(1, routes.size()); + Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); + + mRouter.unregisterCallback(mockCallback); + } + + @Test + public void testOnRouteSelected() throws Exception { MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class); + MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); + + mManager.registerCallback(mExecutor, managerCallback); + mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, mockRouterCallback); - MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { - MediaRoute2Info mSelectedRoute = null; - - @Override - public void onRouteAdded(MediaRoute2Info routeInfo) { - if (mSelectedRoute == null) { - mSelectedRoute = routeInfo; - mManager.selectRoute(mPackageName, mSelectedRoute); - } - } - - @Override - public void onRouteSelected(String packageName, MediaRoute2Info route) { - if (TextUtils.equals(packageName, mPackageName) - && mSelectedRoute != null - && route != null - && TextUtils.equals(route.getId(), mSelectedRoute.getId())) { - latch.countDown(); - } - } - }; + verify(managerCallback, timeout(TIMEOUT_MS)) + .onRoutesChanged(argThat(routes -> routes.size() > 0)); - mManager.registerCallback(mExecutor, managerCallback); + Map<String, MediaRoute2Info> routes = + createRouteMap(mManager.getAvailableRoutes(mPackageName)); - Assert.assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1); + mManager.selectRoute(mPackageName, routeToSelect); + + assertNotNull(routeToSelect); + verify(managerCallback, timeout(TIMEOUT_MS)) + .onRouteAdded(argThat(route -> route.equals(routeToSelect))); mManager.unregisterCallback(managerCallback); + mRouter.unregisterCallback(mockRouterCallback); } /** @@ -219,7 +227,7 @@ public class MediaRouterManagerTest { mRouter.registerCallback(mExecutor, routerCallback); verify(managerCallback, timeout(TIMEOUT_MS)) - .onRouteListChanged(argThat(routes -> routes.size() > 0)); + .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mManager.getAvailableRoutes(mPackageName)); @@ -244,7 +252,8 @@ public class MediaRouterManagerTest { mManager.unregisterCallback(managerCallback); } - Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) { + // Helper for getting routes easily + static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) { Map<String, MediaRoute2Info> routeMap = new HashMap<>(); for (MediaRoute2Info route : routes) { routeMap.put(route.getId(), route); diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index e95103bcb929..7ea83f56557a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -201,6 +201,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard); mBottomNavBarVisible = false; + // Need to initialize screen lifecycle before calling super.start - before switcher is + // created. + mScreenLifecycle = Dependency.get(ScreenLifecycle.class); + mScreenLifecycle.addObserver(mScreenObserver); + super.start(); mTaskStackListener = new TaskStackListenerImpl(); mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); @@ -247,9 +252,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mPowerManagerHelper.connectToCarService(); mSwitchToGuestTimer = new SwitchToGuestTimer(mContext); - - mScreenLifecycle = Dependency.get(ScreenLifecycle.class); - mScreenLifecycle.addObserver(mScreenObserver); } /** @@ -839,7 +841,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt } } - private void attachBottomNavBarWindow() { + /** + * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of + * attaching the bottom nav bar. + */ + protected void attachBottomNavBarWindow() { if (!mShowBottom) { return; } @@ -862,7 +868,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mWindowManager.addView(mNavigationBarWindow, lp); } - private void detachBottomNavBarWindow() { + /** + * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of + * detaching the bottom nav bar. + */ + protected void detachBottomNavBarWindow() { if (!mShowBottom) { return; } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 077f7ecd3e46..cf286bdbde96 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -20,6 +20,8 @@ import android.content.Context; import android.gsi.GsiProgress; import android.net.Uri; import android.os.AsyncTask; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; import android.os.image.DynamicSystemManager; import android.util.Log; import android.webkit.URLUtil; @@ -28,11 +30,9 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.Arrays; import java.util.Locale; import java.util.zip.GZIPInputStream; - class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { private static final String TAG = "InstallationAsyncTask"; @@ -125,28 +125,26 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { Thread.sleep(10); } - if (mInstallationSession == null) { - throw new IOException("Failed to start installation with requested size: " - + (mSystemSize + mUserdataSize)); + throw new IOException( + "Failed to start installation with requested size: " + + (mSystemSize + mUserdataSize)); } installedSize = mUserdataSize; + MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE); byte[] bytes = new byte[READ_BUFFER_SIZE]; - + mInstallationSession.setAshmem( + new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE); int numBytesRead; - Log.d(TAG, "Start installation loop"); while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) { + memoryFile.writeBytes(bytes, 0, 0, numBytesRead); if (isCancelled()) { break; } - - byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE - ? bytes : Arrays.copyOf(bytes, numBytesRead); - - if (!mInstallationSession.write(writeBuffer)) { + if (!mInstallationSession.submitFromAshmem(numBytesRead)) { throw new IOException("Failed write() to DynamicSystem"); } @@ -157,7 +155,6 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { reportedInstalledSize = installedSize; } } - return null; } catch (Exception e) { diff --git a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml index 7846be161c0f..363d88544a03 100644 --- a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1604061903696928905">"Definições de pesquisa"</string> + <string name="search_menu" msgid="1604061903696928905">"Pesquisar definições"</string> </resources> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 13890e029274..f1fc9f9e3d4e 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -452,7 +452,7 @@ <string name="cancel" msgid="6859253417269739139">"বাতিল"</string> <string name="okay" msgid="1997666393121016642">"ঠিক আছে"</string> <string name="zen_mode_enable_dialog_turn_on" msgid="8287824809739581837">"চালু করুন"</string> - <string name="zen_mode_settings_turn_on_dialog_title" msgid="2297134204747331078">"\'বিরক্ত করবেন না\' মোড চালু করুন"</string> + <string name="zen_mode_settings_turn_on_dialog_title" msgid="2297134204747331078">"\'বিরক্ত করবে না\' মোড চালু করুন"</string> <string name="zen_mode_settings_summary_off" msgid="6119891445378113334">"কখনও নয়"</string> <string name="zen_interruption_level_priority" msgid="2078370238113347720">"শুধুমাত্র অগ্রাধিকার"</string> <string name="zen_mode_and_condition" msgid="4927230238450354412">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 2734f4e28632..7874aeb02996 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -286,7 +286,7 @@ <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Esperar que se conecte el depurador para iniciar la aplicación"</string> <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string> <string name="debug_drawing_category" msgid="6755716469267367852">"Dibujo"</string> - <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Representación acelerada mediante hardware"</string> + <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Procesamiento acelerado mediante hardware"</string> <string name="media_category" msgid="4388305075496848353">"Multimedia"</string> <string name="debug_monitoring_category" msgid="7640508148375798343">"Supervisión"</string> <string name="strict_mode" msgid="1938795874357830695">"Modo estricto"</string> diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml index 5cffafed6689..7368f1d105f2 100644 --- a/packages/SettingsLib/res/values-hy/arrays.xml +++ b/packages/SettingsLib/res/values-hy/arrays.xml @@ -43,7 +43,7 @@ <item msgid="8937994881315223448">"Միացված է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ին"</item> <item msgid="1330262655415760617">"Անջատված"</item> <item msgid="7698638434317271902">"Անջատվում է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ից…"</item> - <item msgid="197508606402264311">"Անջատած է"</item> + <item msgid="197508606402264311">"Անջատված է"</item> <item msgid="8578370891960825148">"Անհաջող"</item> <item msgid="5660739516542454527">"Արգելափակված"</item> <item msgid="1805837518286731242">"Վատ ցանցից ժամանակավոր խուսափում"</item> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index bf587405c668..a74b4ae3ae9e 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -263,7 +263,7 @@ <string name="debug_view_attributes" msgid="6485448367803310384">"Միացնել ցուցադրման հատկանիշների ստուգումը"</string> <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Միշտ ակտիվացրած պահել բջջային տվյալները, նույնիսկ Wi‑Fi-ը միացրած ժամանակ (ցանցերի միջև արագ փոխարկման համար):"</string> <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Օգտագործել սարքակազմի արագացման միացումը, եթե հասանելի է"</string> - <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB-ի վրիպազերծումը:"</string> + <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB վրիպազերծումը:"</string> <string name="adb_warning_message" msgid="7316799925425402244">"USB վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string> <string name="adb_keys_warning_message" msgid="5659849457135841625">"Փակե՞լ USB-ի վրիպազերծման մուտքը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string> <string name="dev_settings_warning_title" msgid="7244607768088540165">"Ընդունե՞լ ծրագրավորման կարգավորումներ:"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index cf442b73e721..57e690d46b23 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -159,7 +159,7 @@ <string name="tts_default_pitch_title" msgid="6135942113172488671">"ஒலித்திறன்"</string> <string name="tts_default_pitch_summary" msgid="1944885882882650009">"உருவாக்கப்படும் பேச்சின் டோன் பாதிக்கப்படும்"</string> <string name="tts_default_lang_title" msgid="8018087612299820556">"மொழி"</string> - <string name="tts_lang_use_system" msgid="2679252467416513208">"அமைப்பின் மொழியில்"</string> + <string name="tts_lang_use_system" msgid="2679252467416513208">"அமைப்பின் மொழியைப் பயன்படுத்தவும்"</string> <string name="tts_lang_not_selected" msgid="7395787019276734765">"மொழி தேர்ந்தெடுக்கப்படவில்லை"</string> <string name="tts_default_lang_summary" msgid="5219362163902707785">"பேசப்படும் உரைக்கு மொழி சார்ந்த குரலை அமைக்கிறது"</string> <string name="tts_play_example_title" msgid="7094780383253097230">"எடுத்துக்காட்டைக் கவனிக்கவும்"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 83636dc19beb..eb6160a47fce 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -143,7 +143,7 @@ <string name="data_usage_ota" msgid="5377889154805560860">"సిస్టమ్ అప్డేట్లు"</string> <string name="tether_settings_title_usb" msgid="6688416425801386511">"USB టీథరింగ్"</string> <string name="tether_settings_title_wifi" msgid="3277144155960302049">"పోర్టబుల్ హాట్స్పాట్"</string> - <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"బ్లూటూత్ టీథరింగ్"</string> + <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"బ్లూటూత్ టెథెరింగ్"</string> <string name="tether_settings_title_usb_bluetooth" msgid="5355828977109785001">"టీథరింగ్"</string> <string name="tether_settings_title_all" msgid="8356136101061143841">"టీథరింగ్ & పోర్టబుల్ హాట్స్పాట్"</string> <string name="managed_user_title" msgid="8109605045406748842">"అన్ని కార్యాలయ అనువర్తనాలు"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 4e2d6fa76ad0..5c06c80a049c 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -201,7 +201,7 @@ <string name="vpn_settings_not_available" msgid="956841430176985598">"Cài đặt VPN không khả dụng cho người dùng này"</string> <string name="tethering_settings_not_available" msgid="6765770438438291012">"Cài đặt chia sẻ kết nối không khả dụng cho người dùng này"</string> <string name="apn_settings_not_available" msgid="7873729032165324000">"Cài đặt tên điểm truy cập không khả dụng cho người dùng này"</string> - <string name="enable_adb" msgid="7982306934419797485">"Gỡ lỗi USB"</string> + <string name="enable_adb" msgid="7982306934419797485">"Gỡ lỗi qua USB"</string> <string name="enable_adb_summary" msgid="4881186971746056635">"Bật chế độ gỡ lỗi khi kết nối USB"</string> <string name="clear_adb_keys" msgid="4038889221503122743">"Thu hồi ủy quyền gỡ lỗi USB"</string> <string name="bugreport_in_power" msgid="7923901846375587241">"Phím tắt báo cáo lỗi"</string> @@ -263,7 +263,7 @@ <string name="debug_view_attributes" msgid="6485448367803310384">"Cho phép kiểm tra thuộc tính của chế độ xem"</string> <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Luôn bật dữ liệu di động ngay cả khi Wi-Fi đang hoạt động (để chuyển đổi mạng nhanh)."</string> <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Sử dụng tính năng tăng tốc phần cứng khi chia sẻ kết nối nếu có"</string> - <string name="adb_warning_title" msgid="6234463310896563253">"Cho phép gỡ lỗi USB?"</string> + <string name="adb_warning_title" msgid="6234463310896563253">"Cho phép gỡ lỗi qua USB?"</string> <string name="adb_warning_message" msgid="7316799925425402244">"Gỡ lỗi USB chỉ dành cho mục đích phát triển. Hãy sử dụng tính năng này để sao chép dữ liệu giữa máy tính và thiết bị của bạn, cài đặt ứng dụng trên thiết bị của bạn mà không thông báo và đọc dữ liệu nhật ký."</string> <string name="adb_keys_warning_message" msgid="5659849457135841625">"Thu hồi quyền truy cập gỡ lỗi USB từ tất cả máy tính mà bạn đã ủy quyền trước đó?"</string> <string name="dev_settings_warning_title" msgid="7244607768088540165">"Cho phép cài đặt phát triển?"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index e85199f9ca0d..c64130210d37 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -329,7 +329,7 @@ <string name="show_all_anrs" msgid="4924885492787069007">"顯示背景 ANR"</string> <string name="show_all_anrs_summary" msgid="6636514318275139826">"為背景應用程式顯示「應用程式無回應」對話方塊"</string> <string name="show_notification_channel_warnings" msgid="1399948193466922683">"顯示通知管道警告"</string> - <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"當應用程式未經有效管道發佈通知時,在畫面上顯示警告"</string> + <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"當應用程式未經有效管道發布通知時,在畫面上顯示警告"</string> <string name="force_allow_on_external" msgid="3215759785081916381">"強制允許將應用程式寫入外部儲存空間"</string> <string name="force_allow_on_external_summary" msgid="3640752408258034689">"允許將任何應用程式寫入外部儲存空間 (無論資訊清單值為何)"</string> <string name="force_resizable_activities" msgid="8615764378147824985">"將活動強制設為可調整大小"</string> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index bb9a5e44f72d..b2ff4b3268b2 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -169,6 +169,9 @@ <uses-permission android:name="android.permission.MANAGE_SENSORS" /> <uses-permission android:name="android.permission.MANAGE_AUDIO_POLICY" /> <uses-permission android:name="android.permission.MANAGE_CAMERA" /> + <!-- Permissions needed to test system only camera devices --> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.SYSTEM_CAMERA" /> <!-- Permission needed to enable/disable Bluetooth/Wifi --> <uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" /> <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" /> @@ -235,6 +238,7 @@ <activity android:name=".BugreportWarningActivity" + android:theme="@android:style/Theme.DeviceDefault.Dialog.Alert" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" android:exported="false" /> diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp new file mode 100644 index 000000000000..3be7ca92e8c6 --- /dev/null +++ b/packages/SoundPicker/Android.bp @@ -0,0 +1,18 @@ +android_app { + name: "SoundPicker", + manifest: "AndroidManifest.xml", + + static_libs: [ + "androidx.appcompat_appcompat", + ], + resource_dirs: [ + "res", + ], + srcs: [ + "src/**/*.java", + ], + + platform_apis: true, + certificate: "media", + privileged: true, +} diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml new file mode 100644 index 000000000000..9d0818230a02 --- /dev/null +++ b/packages/SoundPicker/AndroidManifest.xml @@ -0,0 +1,33 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.soundpicker" + android:sharedUserId="android.media"> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + + <application + android:allowBackup="false" + android:supportsRtl="true"> + <receiver android:name="RingtoneReceiver"> + <intent-filter> + <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/> + </intent-filter> + </receiver> + + <service android:name="RingtoneOverlayService" /> + + <activity android:name="RingtonePickerActivity" + android:theme="@style/PickerDialogTheme" + android:enabled="@*android:bool/config_defaultRingtonePickerEnabled" + android:excludeFromRecents="true"> + <intent-filter> + <action android:name="android.intent.action.RINGTONE_PICKER" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/SoundPicker/res/drawable/ic_add.xml b/packages/SoundPicker/res/drawable/ic_add.xml new file mode 100644 index 000000000000..22b3fe9176e5 --- /dev/null +++ b/packages/SoundPicker/res/drawable/ic_add.xml @@ -0,0 +1,24 @@ +<!-- + Copyright (C) 2016 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:fillColor="?android:attr/colorAccent" + android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/> +</vector>
\ No newline at end of file diff --git a/packages/SoundPicker/res/drawable/ic_add_padded.xml b/packages/SoundPicker/res/drawable/ic_add_padded.xml new file mode 100644 index 000000000000..c376867896d0 --- /dev/null +++ b/packages/SoundPicker/res/drawable/ic_add_padded.xml @@ -0,0 +1,22 @@ +<!-- + 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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_add" + android:insetTop="4dp" + android:insetRight="4dp" + android:insetBottom="4dp" + android:insetLeft="4dp"/> diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml new file mode 100644 index 000000000000..6f91d770012a --- /dev/null +++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<!-- + Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent. + Make the visibility to "gone" to prevent failures. + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@null" + android:textColor="?android:attr/colorAccent" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:drawableStart="@drawable/ic_add_padded" + android:drawablePadding="8dp" + android:ellipsize="marquee" + android:visibility="gone" /> diff --git a/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml new file mode 100644 index 000000000000..ee29a3710143 --- /dev/null +++ b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + > + + <CheckedTextView + android:id="@+id/checked_text_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorAlertDialogListItem" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:drawableStart="?android:attr/listChoiceIndicatorSingle" + android:drawablePadding="8dp" + android:ellipsize="marquee" + android:layout_toLeftOf="@+id/work_icon" + android:maxLines="3" /> + + <ImageView + android:id="@id/work_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="20dp" /> +</com.android.soundpicker.CheckedListItem> diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml new file mode 100644 index 000000000000..14421c9a50dc --- /dev/null +++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground"> + +<ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="24dp" + android:layout_marginLeft="24dp" + android:src="@drawable/ic_add" /> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_new_sound_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:text="@null" + android:textColor="?android:attr/colorAccent" + android:textAppearance="?android:attr/textAppearanceMedium" + android:maxLines="3" + android:gravity="center_vertical" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:drawablePadding="20dp" + android:ellipsize="marquee" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml new file mode 100644 index 000000000000..c8ca231f27a4 --- /dev/null +++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + > + + <CheckedTextView + android:id="@+id/checked_text_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorAlertDialogListItem" + android:gravity="center_vertical" + android:paddingStart="20dp" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:drawableStart="?android:attr/listChoiceIndicatorSingle" + android:drawablePadding="20dp" + android:ellipsize="marquee" + android:layout_toLeftOf="@+id/work_icon" + android:maxLines="3" /> + + <ImageView + android:id="@id/work_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="20dp" /> +</com.android.soundpicker.CheckedListItem> diff --git a/packages/SoundPicker/res/raw/default_alarm_alert.ogg b/packages/SoundPicker/res/raw/default_alarm_alert.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker/res/raw/default_alarm_alert.ogg diff --git a/packages/SoundPicker/res/raw/default_notification_sound.ogg b/packages/SoundPicker/res/raw/default_notification_sound.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker/res/raw/default_notification_sound.ogg diff --git a/packages/SoundPicker/res/raw/default_ringtone.ogg b/packages/SoundPicker/res/raw/default_ringtone.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker/res/raw/default_ringtone.ogg diff --git a/packages/SoundPicker/res/values-watch/config.xml b/packages/SoundPicker/res/values-watch/config.xml new file mode 100644 index 000000000000..0bc24fa0c481 --- /dev/null +++ b/packages/SoundPicker/res/values-watch/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. Do not translate. + + NOTE: The naming convention is "config_camelCaseValue". --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the + ringtone will be automatically selected when the picker is closed. --> + <bool name="config_showOkCancelButtons">false</bool> +</resources> diff --git a/packages/SoundPicker/res/values/config.xml b/packages/SoundPicker/res/values/config.xml new file mode 100644 index 000000000000..4e237a2f1644 --- /dev/null +++ b/packages/SoundPicker/res/values/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. Do not translate. + + NOTE: The naming convention is "config_camelCaseValue". --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the + ringtone will be automatically selected when the picker is closed. --> + <bool name="config_showOkCancelButtons">true</bool> +</resources> diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml new file mode 100644 index 000000000000..56ed5fd5d58e --- /dev/null +++ b/packages/SoundPicker/res/values/strings.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Choice in the ringtone picker. If chosen, the default ringtone will be used. --> + <string name="ringtone_default">Default ringtone</string> + + <!-- Choice in the notification sound picker. If chosen, the default notification sound will be + used. --> + <string name="notification_sound_default">Default notification sound</string> + + <!-- Choice in the alarm sound picker. If chosen, the default alarm sound will be used. --> + <string name="alarm_sound_default">Default alarm sound</string> + + <!-- Text for the RingtonePicker item that allows adding a new ringtone. --> + <string name="add_ringtone_text">Add ringtone</string> + <!-- Text for the RingtonePicker item that allows adding a new alarm. --> + <string name="add_alarm_text">Add alarm</string> + <!-- Text for the RingtonePicker item that allows adding a new notification. --> + <string name="add_notification_text">Add notification</string> + <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. --> + <string name="delete_ringtone_text">Delete</string> + <!-- Text for the Toast displayed when adding a custom ringtone fails. --> + <string name="unable_to_add_ringtone">Unable to add custom ringtone</string> + <!-- Text for the Toast displayed when deleting a custom ringtone fails. --> + <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string> +</resources> diff --git a/packages/SoundPicker/res/values/styles.xml b/packages/SoundPicker/res/values/styles.xml new file mode 100644 index 000000000000..d22d9c43d0fb --- /dev/null +++ b/packages/SoundPicker/res/values/styles.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog"> + </style> + +</resources> diff --git a/packages/SoundPicker/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker/src/com/android/soundpicker/CheckedListItem.java new file mode 100644 index 000000000000..bde87cfc39ef --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/CheckedListItem.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 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.soundpicker; + +import android.content.Context; +import android.widget.Checkable; +import android.widget.CheckedTextView; +import android.widget.RelativeLayout; +import android.util.AttributeSet; + +/** + * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in + * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the + * name if the ringtone belongs to a work profile. + */ +public class CheckedListItem extends RelativeLayout implements Checkable { + + public CheckedListItem(Context context) { + super(context); + } + + public CheckedListItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void setChecked(boolean checked) { + getCheckedTextView().setChecked(checked); + } + + @Override + public boolean isChecked() { + return getCheckedTextView().isChecked(); + } + + @Override + public void toggle() { + getCheckedTextView().toggle(); + } + + private CheckedTextView getCheckedTextView() { + return (CheckedTextView) findViewById(R.id.checked_text_view); + } + +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java new file mode 100644 index 000000000000..2d37b4c9c342 --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java @@ -0,0 +1,113 @@ +/* + * 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 com.android.soundpicker; + +import android.app.Service; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.FileUtils; +import android.os.IBinder; +import android.provider.MediaStore; +import android.provider.Settings.System; +import android.util.Log; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Service to copy and set customization of default sounds + */ +public class RingtoneOverlayService extends Service { + private static final String TAG = "RingtoneOverlayService"; + private static final boolean DEBUG = false; + + @Override + public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) { + AsyncTask.execute(() -> { + updateRingtones(); + stopSelf(); + }); + + // Try again later if we are killed before we finish. + return Service.START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(@Nullable final Intent intent) { + return null; + } + + private void updateRingtones() { + copyResourceAndSetAsSound(R.raw.default_ringtone, + System.RINGTONE, Environment.DIRECTORY_RINGTONES); + copyResourceAndSetAsSound(R.raw.default_notification_sound, + System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS); + copyResourceAndSetAsSound(R.raw.default_alarm_alert, + System.ALARM_ALERT, Environment.DIRECTORY_ALARMS); + } + + /* If the resource contains any data, copy a resource to the file system, scan it, and set the + * file URI as the default for a sound. */ + private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name, + @NonNull final String subPath) { + final File destDir = Environment.getExternalStoragePublicDirectory(subPath); + if (!destDir.exists() && !destDir.mkdirs()) { + Log.e(TAG, "can't create " + destDir.getAbsolutePath()); + return; + } + + final File dest = new File(destDir, "default_" + name + ".ogg"); + try ( + InputStream is = getResources().openRawResource(id); + FileOutputStream os = new FileOutputStream(dest); + ) { + if (is.available() > 0) { + FileUtils.copy(is, os); + final Uri uri = scanFile(dest); + if (uri != null) { + set(name, uri); + } + } else { + // TODO Shall we remove any former copied resource in this case and unset + // the defaults if we use this event a second time to clear the data? + if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay"); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource for " + name + ": " + e); + } + } + + private Uri scanFile(@NonNull final File file) { + return MediaStore.scanFile(this, file); + } + + private void set(@NonNull final String name, @NonNull final Uri uri) { + final Uri settingUri = System.getUriFor(name); + RingtoneManager.setActualDefaultRingtoneUri(this, + RingtoneManager.getDefaultType(settingUri), uri); + System.putInt(getContentResolver(), name + "_set", 1); + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java new file mode 100644 index 000000000000..4ba5146b8d39 --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2007 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.soundpicker; + +import android.content.ContentProvider; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.media.AudioAttributes; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.MediaStore; +import android.provider.Settings; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * The {@link RingtonePickerActivity} allows the user to choose one from all of the + * available ringtones. The chosen ringtone's URI will be persisted as a string. + * + * @see RingtoneManager#ACTION_RINGTONE_PICKER + */ +public final class RingtonePickerActivity extends AlertActivity implements + AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener, + AlertController.AlertParams.OnPrepareListViewListener { + + private static final int POS_UNKNOWN = -1; + + private static final String TAG = "RingtonePickerActivity"; + + private static final int DELAY_MS_SELECTION_PLAYED = 300; + + private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE; + + private static final String SAVE_CLICKED_POS = "clicked_pos"; + + private static final String SOUND_NAME_RES_PREFIX = "sound_name_"; + + private static final int ADD_FILE_REQUEST_CODE = 300; + + private RingtoneManager mRingtoneManager; + private int mType; + + private Cursor mCursor; + private Handler mHandler; + private BadgedRingtoneAdapter mAdapter; + + /** The position in the list of the 'Silent' item. */ + private int mSilentPos = POS_UNKNOWN; + + /** The position in the list of the 'Default' item. */ + private int mDefaultRingtonePos = POS_UNKNOWN; + + /** The position in the list of the ringtone to sample. */ + private int mSampleRingtonePos = POS_UNKNOWN; + + /** Whether this list has the 'Silent' item. */ + private boolean mHasSilentItem; + + /** The Uri to place a checkmark next to. */ + private Uri mExistingUri; + + /** The number of static items in the list. */ + private int mStaticItemCount; + + /** Whether this list has the 'Default' item. */ + private boolean mHasDefaultItem; + + /** The Uri to play when the 'Default' item is clicked. */ + private Uri mUriForDefaultItem; + + /** Id of the user to which the ringtone picker should list the ringtones */ + private int mPickerUserId; + + /** Context of the user specified by mPickerUserId */ + private Context mTargetContext; + + /** + * A Ringtone for the default ringtone. In most cases, the RingtoneManager + * will stop the previous ringtone. However, the RingtoneManager doesn't + * manage the default ringtone for us, so we should stop this one manually. + */ + private Ringtone mDefaultRingtone; + + /** + * The ringtone that's currently playing, unless the currently playing one is the default + * ringtone. + */ + private Ringtone mCurrentRingtone; + + /** + * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked). + */ + private long mCheckedItemId = -1; + + private int mAttributesFlags; + + private boolean mShowOkCancelButtons; + + /** + * Keep the currently playing ringtone around when changing orientation, so that it + * can be stopped later, after the activity is recreated. + */ + private static Ringtone sPlayingRingtone; + + private DialogInterface.OnClickListener mRingtoneClickListener = + new DialogInterface.OnClickListener() { + + /* + * On item clicked + */ + public void onClick(DialogInterface dialog, int which) { + if (which == mCursor.getCount() + mStaticItemCount) { + // The "Add new ringtone" item was clicked. Start a file picker intent to select + // only audio files (MIME type "audio/*") + final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); + chooseFile.setType("audio/*"); + chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, + new String[] { "audio/*", "application/ogg" }); + startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE); + return; + } + + // Save the position of most recently clicked item + setCheckedItem(which); + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + + // Play clip + playRingtone(which, 0); + } + + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHandler = new Handler(); + + Intent intent = getIntent(); + mPickerUserId = UserHandle.myUserId(); + mTargetContext = this; + + // Get the types of ringtones to show + mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1); + initRingtoneManager(); + + /* + * Get whether to show the 'Default' item, and the URI to play when the + * default is clicked + */ + mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); + if (mUriForDefaultItem == null) { + if (mType == RingtoneManager.TYPE_NOTIFICATION) { + mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI; + } else if (mType == RingtoneManager.TYPE_ALARM) { + mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI; + } else if (mType == RingtoneManager.TYPE_RINGTONE) { + mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; + } else { + // or leave it null for silence. + mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; + } + } + + // Get whether to show the 'Silent' item + mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + // AudioAttributes flags + mAttributesFlags |= intent.getIntExtra( + RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, + 0 /*defaultValue == no flags*/); + + mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); + + // The volume keys will control the stream that we are choosing a ringtone for + setVolumeControlStream(mRingtoneManager.inferStreamType()); + + // Get the URI whose list item should have a checkmark + mExistingUri = intent + .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); + + // Create the list of ringtones and hold on to it so we can update later. + mAdapter = new BadgedRingtoneAdapter(this, mCursor, + /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId)); + if (savedInstanceState != null) { + setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN)); + } + + final AlertController.AlertParams p = mAlertParams; + p.mAdapter = mAdapter; + p.mOnClickListener = mRingtoneClickListener; + p.mLabelColumn = COLUMN_LABEL; + p.mIsSingleChoice = true; + p.mOnItemSelectedListener = this; + if (mShowOkCancelButtons) { + p.mPositiveButtonText = getString(com.android.internal.R.string.ok); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(com.android.internal.R.string.cancel); + p.mPositiveButtonListener = this; + } + p.mOnPrepareListViewListener = this; + + p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); + if (p.mTitle == null) { + if (mType == RingtoneManager.TYPE_ALARM) { + p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm); + } else if (mType == RingtoneManager.TYPE_NOTIFICATION) { + p.mTitle = + getString(com.android.internal.R.string.ringtone_picker_title_notification); + } else { + p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title); + } + } + + setupAlert(); + } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_CLICKED_POS, getCheckedItem()); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) { + // Add the custom ringtone in a separate thread + final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() { + @Override + protected Uri doInBackground(Uri... params) { + try { + return mRingtoneManager.addCustomExternalRingtone(params[0], mType); + } catch (IOException | IllegalArgumentException e) { + Log.e(TAG, "Unable to add new ringtone", e); + } + return null; + } + + @Override + protected void onPostExecute(Uri ringtoneUri) { + if (ringtoneUri != null) { + requeryForAdapter(); + } else { + // Ringtone was not added, display error Toast + Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone, + Toast.LENGTH_SHORT).show(); + } + } + }; + installTask.execute(data.getData()); + } + } + + // Disabled because context menus aren't Material Design :( + /* + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + int position = ((AdapterContextMenuInfo) menuInfo).position; + + Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position)); + if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) { + // It's a custom ringtone so we display the context menu + menu.setHeaderTitle(ringtone.getTitle(this)); + menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Menu.FIRST: { + int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position; + Uri deletedRingtoneUri = getRingtone( + getRingtoneManagerPosition(deletedRingtonePos)).getUri(); + if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) { + requeryForAdapter(); + } else { + Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT) + .show(); + } + return true; + } + default: { + return false; + } + } + } + */ + + @Override + public void onDestroy() { + if (mCursor != null) { + mCursor.close(); + mCursor = null; + } + super.onDestroy(); + } + + public void onPrepareListView(ListView listView) { + // Reset the static item count, as this method can be called multiple times + mStaticItemCount = 0; + + if (mHasDefaultItem) { + mDefaultRingtonePos = addDefaultRingtoneItem(listView); + + if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) { + setCheckedItem(mDefaultRingtonePos); + } + } + + if (mHasSilentItem) { + mSilentPos = addSilentItem(listView); + + // The 'Silent' item should use a null Uri + if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) { + setCheckedItem(mSilentPos); + } + } + + if (getCheckedItem() == POS_UNKNOWN) { + setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri))); + } + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + // If external storage is available, add a button to install sounds from storage. + if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + addNewSoundItem(listView); + } + + // Enable context menu in ringtone items + registerForContextMenu(listView); + } + + /** + * Re-query RingtoneManager for the most recent set of installed ringtones. May move the + * selected item position to match the new position of the chosen sound. + * + * This should only need to happen after adding or removing a ringtone. + */ + private void requeryForAdapter() { + // Refresh and set a new cursor, closing the old one. + initRingtoneManager(); + mAdapter.changeCursor(mCursor); + + // Update checked item location. + int checkedPosition = POS_UNKNOWN; + for (int i = 0; i < mAdapter.getCount(); i++) { + if (mAdapter.getItemId(i) == mCheckedItemId) { + checkedPosition = getListPosition(i); + break; + } + } + if (mHasSilentItem && checkedPosition == POS_UNKNOWN) { + checkedPosition = mSilentPos; + } + setCheckedItem(checkedPosition); + setupAlert(); + } + + /** + * Adds a static item to the top of the list. A static item is one that is not from the + * RingtoneManager. + * + * @param listView The ListView to add to. + * @param textResId The resource ID of the text for the item. + * @return The position of the inserted item. + */ + private int addStaticItem(ListView listView, int textResId) { + TextView textView = (TextView) getLayoutInflater().inflate( + com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false); + textView.setText(textResId); + listView.addHeaderView(textView); + mStaticItemCount++; + return listView.getHeaderViewsCount() - 1; + } + + private int addDefaultRingtoneItem(ListView listView) { + if (mType == RingtoneManager.TYPE_NOTIFICATION) { + return addStaticItem(listView, R.string.notification_sound_default); + } else if (mType == RingtoneManager.TYPE_ALARM) { + return addStaticItem(listView, R.string.alarm_sound_default); + } + + return addStaticItem(listView, R.string.ringtone_default); + } + + private int addSilentItem(ListView listView) { + return addStaticItem(listView, com.android.internal.R.string.ringtone_silent); + } + + private void addNewSoundItem(ListView listView) { + View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView, + false /* attachToRoot */); + TextView text = (TextView)view.findViewById(R.id.add_new_sound_text); + + if (mType == RingtoneManager.TYPE_ALARM) { + text.setText(R.string.add_alarm_text); + } else if (mType == RingtoneManager.TYPE_NOTIFICATION) { + text.setText(R.string.add_notification_text); + } else { + text.setText(R.string.add_ringtone_text); + } + listView.addFooterView(view); + } + + private void initRingtoneManager() { + // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it + // causes unexpected behavior. + mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true); + if (mType != -1) { + mRingtoneManager.setType(mType); + } + mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL); + } + + private Ringtone getRingtone(int ringtoneManagerPosition) { + if (ringtoneManagerPosition < 0) { + return null; + } + return mRingtoneManager.getRingtone(ringtoneManagerPosition); + } + + private int getCheckedItem() { + return mAlertParams.mCheckedItem; + } + + private void setCheckedItem(int pos) { + mAlertParams.mCheckedItem = pos; + mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos)); + } + + /* + * On click of Ok/Cancel buttons + */ + public void onClick(DialogInterface dialog, int which) { + boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; + + // Stop playing the previous ringtone + mRingtoneManager.stopPreviousRingtone(); + + if (positiveResult) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } else { + setResult(RESULT_CANCELED); + } + + finish(); + } + + /* + * On item selected via keys + */ + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // footer view + if (position >= mCursor.getCount() + mStaticItemCount) { + return; + } + + playRingtone(position, DELAY_MS_SELECTION_PLAYED); + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + } + + public void onNothingSelected(AdapterView parent) { + } + + private void playRingtone(int position, int delayMs) { + mHandler.removeCallbacks(this); + mSampleRingtonePos = position; + mHandler.postDelayed(this, delayMs); + } + + public void run() { + stopAnyPlayingRingtone(); + if (mSampleRingtonePos == mSilentPos) { + return; + } + + Ringtone ringtone; + if (mSampleRingtonePos == mDefaultRingtonePos) { + if (mDefaultRingtone == null) { + mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem); + } + /* + * Stream type of mDefaultRingtone is not set explicitly here. + * It should be set in accordance with mRingtoneManager of this Activity. + */ + if (mDefaultRingtone != null) { + mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType()); + } + ringtone = mDefaultRingtone; + mCurrentRingtone = null; + } else { + ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos)); + mCurrentRingtone = ringtone; + } + + if (ringtone != null) { + if (mAttributesFlags != 0) { + ringtone.setAudioAttributes( + new AudioAttributes.Builder(ringtone.getAudioAttributes()) + .setFlags(mAttributesFlags) + .build()); + } + ringtone.play(); + } + } + + @Override + protected void onStop() { + super.onStop(); + + if (!isChangingConfigurations()) { + stopAnyPlayingRingtone(); + } else { + saveAnyPlayingRingtone(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (!isChangingConfigurations()) { + stopAnyPlayingRingtone(); + } + } + + private void setSuccessResultWithRingtone(Uri ringtoneUri) { + setResult(RESULT_OK, + new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri)); + } + + private Uri getCurrentlySelectedRingtoneUri() { + if (getCheckedItem() == mDefaultRingtonePos) { + // Use the default Uri that they originally gave us. + return mUriForDefaultItem; + } else if (getCheckedItem() == mSilentPos) { + // Use a null Uri for the 'Silent' item. + return null; + } else { + return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem())); + } + } + + private void saveAnyPlayingRingtone() { + if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { + sPlayingRingtone = mDefaultRingtone; + } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) { + sPlayingRingtone = mCurrentRingtone; + } + } + + private void stopAnyPlayingRingtone() { + if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) { + sPlayingRingtone.stop(); + } + sPlayingRingtone = null; + + if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { + mDefaultRingtone.stop(); + } + + if (mRingtoneManager != null) { + mRingtoneManager.stopPreviousRingtone(); + } + } + + private int getRingtoneManagerPosition(int listPos) { + return listPos - mStaticItemCount; + } + + private int getListPosition(int ringtoneManagerPos) { + + // If the manager position is -1 (for not found), return that + if (ringtoneManagerPos < 0) return ringtoneManagerPos; + + return ringtoneManagerPos + mStaticItemCount; + } + + private static class LocalizedCursor extends CursorWrapper { + + final int mTitleIndex; + final Resources mResources; + String mNamePrefix; + final Pattern mSanitizePattern; + + LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) { + super(cursor); + mTitleIndex = mCursor.getColumnIndex(columnLabel); + mResources = resources; + mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]"); + if (mTitleIndex == -1) { + Log.e(TAG, "No index for column " + columnLabel); + mNamePrefix = null; + } else { + try { + // Build the prefix for the name of the resource to look up + // format is: "ResourcePackageName::ResourceTypeName/" + // (the type name is expected to be "string" but let's not hardcode it). + // Here we use an existing resource "notification_sound_default" which is + // always expected to be found. + mNamePrefix = String.format("%s:%s/%s", + mResources.getResourcePackageName(R.string.notification_sound_default), + mResources.getResourceTypeName(R.string.notification_sound_default), + SOUND_NAME_RES_PREFIX); + } catch (NotFoundException e) { + mNamePrefix = null; + } + } + } + + /** + * Process resource name to generate a valid resource name. + * @param input + * @return a non-null String + */ + private String sanitize(String input) { + if (input == null) { + return ""; + } + return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(); + } + + @Override + public String getString(int columnIndex) { + final String defaultName = mCursor.getString(columnIndex); + if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) { + return defaultName; + } + TypedValue value = new TypedValue(); + try { + // the name currently in the database is used to derive a name to match + // against resource names in this package + mResources.getValue(mNamePrefix + sanitize(defaultName), value, false); + } catch (NotFoundException e) { + // no localized string, use the default string + return defaultName; + } + if ((value != null) && (value.type == TypedValue.TYPE_STRING)) { + Log.d(TAG, String.format("Replacing name %s with %s", + defaultName, value.string.toString())); + return value.string.toString(); + } else { + Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName); + return defaultName; + } + } + } + + private class BadgedRingtoneAdapter extends CursorAdapter { + private final boolean mIsManagedProfile; + + public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) { + super(context, cursor); + mIsManagedProfile = isManagedProfile; + } + + @Override + public long getItemId(int position) { + if (position < 0) { + return position; + } + return super.getItemId(position); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(R.layout.radio_with_work_badge, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Set text as the title of the ringtone + ((TextView) view.findViewById(R.id.checked_text_view)) + .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)); + + boolean isWorkRingtone = false; + if (mIsManagedProfile) { + /* + * Display the work icon if the ringtone belongs to a work profile. We can tell that + * a ringtone belongs to a work profile if the picker user is a managed profile, the + * ringtone Uri is in external storage, and either the uri has no user id or has the + * id of the picker user + */ + Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition()); + int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId); + Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri); + + if (uriUserId == mPickerUserId && uriWithoutUserId.toString() + .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) { + isWorkRingtone = true; + } + } + + ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon); + if(isWorkRingtone) { + workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground( + UserHandle.of(mPickerUserId), -1 /* density */)); + workIcon.setVisibility(View.VISIBLE); + } else { + workIcon.setVisibility(View.GONE); + } + } + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java new file mode 100644 index 000000000000..9e1ba3a2898c --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java @@ -0,0 +1,37 @@ +/* //device/content/providers/media/src/com/android/providers/media/MediaScannerReceiver.java +** +** Copyright 2007, 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.soundpicker; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class RingtoneReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) { + initResourceRingtones(context); + } + } + + private void initResourceRingtones(Context context) { + context.startService( + new Intent(context, RingtoneOverlayService.class)); + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java new file mode 100644 index 000000000000..cac673fdbf0b --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java @@ -0,0 +1,36 @@ +/* + * 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 com.android.systemui.plugins; + +import android.view.ViewGroup; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Test plugin for home controls + */ +@ProvidesInterface(action = HomeControlsPlugin.ACTION, version = HomeControlsPlugin.VERSION) +public interface HomeControlsPlugin extends Plugin { + + String ACTION = "com.android.systemui.action.PLUGIN_HOME_CONTROLS"; + int VERSION = 1; + + /** + * Pass the container for the plugin to use however it wants. Ideally the plugin impl + * will add home controls to this space. + */ + void sendParentGroup(ViewGroup group); +} diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 7d403b24354c..7a9400887c75 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -55,6 +55,19 @@ android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" /> + <!-- Temporary area to test out home controls --> + <LinearLayout + android:id="@+id/home_controls_layout" + android:layout_width="match_parent" + android:layout_height="125dp" + android:layout_gravity="@integer/notification_panel_layout_gravity" + android:visibility="gone" + android:padding="8dp" + android:layout_margin="5dp" + android:background="?android:attr/colorBackgroundFloating" + android:orientation="vertical"> + </LinearLayout> + <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout android:id="@+id/notification_stack_scroller" android:layout_marginTop="@dimen/notification_panel_margin_top" diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index 10d132ad2763..45126f35d864 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -19,16 +19,12 @@ package com.android.keyguard; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static android.telephony.PhoneStateListener.LISTEN_NONE; -import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; - import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Handler; -import android.os.SystemProperties; -import android.telephony.CarrierConfigManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -37,19 +33,17 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.VisibleForTesting; - import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; import com.android.settingslib.WirelessUtils; import com.android.systemui.Dependency; import com.android.systemui.keyguard.WakefulnessLifecycle; -import java.util.ArrayList; import java.util.List; import java.util.Objects; +import androidx.annotation.VisibleForTesting; + /** * Controller that generates text including the carrier names and/or the status of all the SIM * interfaces in the device. Through a callback, the updates can be retrieved either as a list or @@ -72,8 +66,6 @@ public class CarrierTextController { private Context mContext; private CharSequence mSeparator; private WakefulnessLifecycle mWakefulnessLifecycle; - @VisibleForTesting - protected boolean mDisplayOpportunisticSubscriptionCarrierText; private final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override @@ -174,9 +166,6 @@ public class CarrierTextController { mSimSlotsNumber = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE)).getPhoneCount(); mSimErrorState = new boolean[mSimSlotsNumber]; - updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean( - TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME, - false)); } /** @@ -253,63 +242,8 @@ public class CarrierTextController { } } - /** - * @param subscriptions - */ - private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) { - if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { - SubscriptionInfo info1 = subscriptions.get(0); - SubscriptionInfo info2 = subscriptions.get(1); - if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { - // If both subscriptions are primary, show both. - if (!info1.isOpportunistic() && !info2.isOpportunistic()) return; - - // If carrier required, always show signal bar of primary subscription. - // Otherwise, show whichever subscription is currently active for Internet. - boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig() - .getBoolean(CarrierConfigManager - .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN); - if (alwaysShowPrimary) { - subscriptions.remove(info1.isOpportunistic() ? info1 : info2); - } else { - subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription - ? info2 : info1); - } - - } - } - } - - /** - * updates if opportunistic sub carrier text should be displayed or not - * - */ - @VisibleForTesting - public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) { - mDisplayOpportunisticSubscriptionCarrierText = isEnable; - } - protected List<SubscriptionInfo> getSubscriptionInfo() { - List<SubscriptionInfo> subs; - if (mDisplayOpportunisticSubscriptionCarrierText) { - SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext - .getSystemService( - Context.TELEPHONY_SUBSCRIPTION_SERVICE)); - subs = subscriptionManager.getActiveSubscriptionInfoList(false); - if (subs == null) { - subs = new ArrayList<>(); - } else { - filterMobileSubscriptionInSameGroup(subs); - } - } else { - subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); - if (subs == null) { - subs = new ArrayList<>(); - } else { - filterMobileSubscriptionInSameGroup(subs); - } - } - return subs; + return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); } protected void updateCarrierText() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8c5374a2a3fe..5d6cc835bf5c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -30,6 +30,7 @@ import static android.os.BatteryManager.EXTRA_PLUGGED; import static android.os.BatteryManager.EXTRA_STATUS; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; +import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -77,6 +78,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.telephony.CarrierConfigManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -261,6 +263,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private boolean mLogoutEnabled; // If the user long pressed the lock icon, disabling face auth for the current session. private boolean mLockIconPressed; + private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; /** * Short delay before restarting biometric authentication after a successful try @@ -406,9 +409,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } }; - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @VisibleForTesting + public PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveMobileDataSubscription = subId; mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED); } }; @@ -511,7 +516,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - /** @return List of SubscriptionInfo records, maybe empty but never null */ + /** + * @return List of SubscriptionInfo records, maybe empty but never null. + */ public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) { List<SubscriptionInfo> sil = mSubscriptionInfo; if (sil == null || forceReload) { @@ -523,7 +530,42 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } else { mSubscriptionInfo = sil; } - return mSubscriptionInfo; + return new ArrayList<>(mSubscriptionInfo); + } + + /** + * This method returns filtered list of SubscriptionInfo from {@link #getSubscriptionInfo}. + * above. Maybe empty but never null. + * + * In DSDS mode if both subscriptions are grouped and one is opportunistic, we filter out one + * of them based on carrier config. e.g. In this case we should only show one carrier name + * on the status bar and quick settings. + */ + public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) { + List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false); + if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { + SubscriptionInfo info1 = subscriptions.get(0); + SubscriptionInfo info2 = subscriptions.get(1); + if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { + // If both subscriptions are primary, show both. + if (!info1.isOpportunistic() && !info2.isOpportunistic()) return subscriptions; + + // If carrier required, always show signal bar of primary subscription. + // Otherwise, show whichever subscription is currently active for Internet. + boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig() + .getBoolean(CarrierConfigManager + .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN); + if (alwaysShowPrimary) { + subscriptions.remove(info1.isOpportunistic() ? info1 : info2); + } else { + subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription + ? info2 : info1); + } + + } + } + + return subscriptions; } @Override @@ -2700,6 +2742,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" " + mSubscriptionInfo.get(i)); } } + pw.println(" Current active data subId=" + mActiveMobileDataSubscription); pw.println(" Service states:"); for (int subId : mServiceStates.keySet()) { pw.println(" " + subId + "=" + mServiceStates.get(subId)); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 7e3b42389637..d5c928b4d198 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -140,11 +140,12 @@ public class SystemUIFactory { LockPatternUtils lockPatternUtils, ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry, KeyguardBouncer.BouncerExpansionCallback expansionCallback, - FalsingManager falsingManager) { + FalsingManager falsingManager, KeyguardBypassController bypassController) { return new KeyguardBouncer(context, callback, lockPatternUtils, container, dismissCallbackRegistry, falsingManager, expansionCallback, UnlockMethodCache.getInstance(context), - KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper())); + KeyguardUpdateMonitor.getInstance(context), bypassController, + new Handler(Looper.getMainLooper())); } public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront, diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 858ed6dcf93d..ef171d305d28 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -50,7 +50,7 @@ import javax.inject.Singleton; */ @Singleton public class AppOpsControllerImpl implements AppOpsController, - AppOpsManager.OnOpActiveChangedListener, + AppOpsManager.OnOpActiveChangedInternalListener, AppOpsManager.OnOpNotedListener, Dumpable { private static final long NOTED_OP_TIME_DELAY_MS = 5000; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index ee79e6b973de..41203348d7a0 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -52,16 +52,17 @@ public class FalsingManagerProxy implements FalsingManager { private FalsingManager mInternalFalsingManager; private final Handler mMainHandler; + private boolean mBrightlineEnabled; @Inject FalsingManagerProxy(Context context, PluginManager pluginManager, @Named(MAIN_HANDLER_NAME) Handler handler) { mMainHandler = handler; + setupFalsingManager(context); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, command -> mMainHandler.post(command), properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace()) ); - setupFalsingManager(context); final PluginListener<FalsingPlugin> mPluginListener = new PluginListener<FalsingPlugin>() { public void onPluginConnected(FalsingPlugin plugin, Context context) { FalsingManager pluginFalsingManager = plugin.getFalsingManager(context); @@ -94,6 +95,10 @@ public class FalsingManagerProxy implements FalsingManager { public void setupFalsingManager(Context context) { boolean brightlineEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_MANAGER_ENABLED, true); + if (brightlineEnabled == mBrightlineEnabled && mInternalFalsingManager != null) { + return; + } + mBrightlineEnabled = brightlineEnabled; if (mInternalFalsingManager != null) { mInternalFalsingManager.cleanup(); diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java index d74112608491..aac721e3cb56 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java @@ -146,7 +146,13 @@ public class EglHelper { * @return true if EglSurface is ready. */ public boolean createEglSurface(SurfaceHolder surfaceHolder) { - mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0); + if (hasEglDisplay()) { + mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0); + } else { + Log.w(TAG, "mEglDisplay is null"); + return false; + } + if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; @@ -186,7 +192,13 @@ public class EglHelper { public boolean createEglContext() { int[] attrib_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_NONE}; - mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0); + if (hasEglDisplay()) { + mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0); + } else { + Log.w(TAG, "mEglDisplay is null"); + return false; + } + if (mEglContext == EGL_NO_CONTEXT) { Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; @@ -213,6 +225,14 @@ public class EglHelper { } /** + * Check if we have EglDisplay. + * @return true if EglDisplay is ready. + */ + public boolean hasEglDisplay() { + return mEglDisplay != null; + } + + /** * Swap buffer to display. * @return true if swap successfully. */ @@ -235,7 +255,9 @@ public class EglHelper { if (hasEglContext()) { destroyEglContext(); } - eglTerminate(mEglDisplay); + if (hasEglDisplay()) { + eglTerminate(mEglDisplay); + } mEglReady = false; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index d47288a706a9..8670d1bd0ce9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -87,6 +87,7 @@ import com.android.systemui.util.NotificationChannels; import libcore.io.IoUtils; +import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; @@ -254,8 +255,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Save the screenshot to the MediaStore final MediaStore.PendingParams params = new MediaStore.PendingParams( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png"); - params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES); - params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS); + params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator + + Environment.DIRECTORY_SCREENSHOTS); final Uri uri = MediaStore.createPending(context, params); final MediaStore.PendingSession session = MediaStore.openPending(context, uri); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java index d1b3c3cb12d8..2a5ccdb0c0a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java @@ -134,7 +134,7 @@ public class OperatorNameView extends TextView implements DemoMode, DarkReceiver private void updateText() { CharSequence displayText = null; - List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); + List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); final int N = subs.size(); for (int i = 0; i < N; i++) { int subId = subs.get(i).getSubscriptionId(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java index d9328fa3affd..e4bd4fa1ae75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java @@ -107,7 +107,7 @@ public class DynamicPrivacyController implements UnlockMethodCache.OnUnlockMetho */ public boolean isInLockedDownShade() { if (!mStatusBarKeyguardViewManager.isShowing() - || !mStatusBarKeyguardViewManager.isSecure()) { + || !mUnlockMethodCache.isMethodSecure()) { return false; } int state = mStateController.getState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index c4d346ccaefb..dc9b373de688 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -77,6 +77,7 @@ public class KeyguardBouncer { } }; private final Runnable mRemoveViewRunnable = this::removeView; + private final KeyguardBypassController mKeyguardBypassController; protected KeyguardHostView mKeyguardView; private final Runnable mResetRunnable = ()-> { if (mKeyguardView != null) { @@ -97,7 +98,8 @@ public class KeyguardBouncer { LockPatternUtils lockPatternUtils, ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager, BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache, - KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + KeyguardBypassController keyguardBypassController, Handler handler) { mContext = context; mCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -109,6 +111,7 @@ public class KeyguardBouncer { mHandler = handler; mUnlockMethodCache = unlockMethodCache; mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + mKeyguardBypassController = keyguardBypassController; } public void show(boolean resetSecuritySelection) { @@ -171,7 +174,8 @@ public class KeyguardBouncer { // Split up the work over multiple frames. DejankUtils.removeCallbacks(mResetRunnable); if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer() - && !mKeyguardUpdateMonitor.userNeedsStrongAuth()) { + && !mKeyguardUpdateMonitor.userNeedsStrongAuth() + && !mKeyguardBypassController.getBypassEnabled()) { mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); } else { DejankUtils.postAfterTraversal(mShowRunnable); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index c17173063fc9..c76cdcb85c34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -53,6 +53,7 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; +import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -68,10 +69,13 @@ import com.android.systemui.R; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.HomeControlsPlugin; +import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qs.QSFragment; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; @@ -192,6 +196,7 @@ public class NotificationPanelView extends PanelView implements private View mQsNavbarScrim; protected NotificationsQuickSettingsContainer mNotificationContainerParent; protected NotificationStackScrollLayout mNotificationStackScroller; + protected LinearLayout mHomeControlsLayout; private boolean mAnimateNextPositionUpdate; private int mTrackingPointer; @@ -450,6 +455,7 @@ public class NotificationPanelView extends PanelView implements mBigClockContainer = findViewById(R.id.big_clock_container); keyguardClockSwitch.setBigClockContainer(mBigClockContainer); + mHomeControlsLayout = findViewById(R.id.home_controls_layout); mNotificationContainerParent = findViewById(R.id.notification_container_parent); mNotificationStackScroller = findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(this); @@ -480,6 +486,21 @@ public class NotificationPanelView extends PanelView implements } } }); + + Dependency.get(PluginManager.class).addPluginListener( + new PluginListener<HomeControlsPlugin>() { + + @Override + public void onPluginConnected(HomeControlsPlugin plugin, + Context pluginContext) { + plugin.sendParentGroup(mHomeControlsLayout); + } + + @Override + public void onPluginDisconnected(HomeControlsPlugin plugin) { + + } + }, HomeControlsPlugin.class, false); } @Override @@ -1270,9 +1291,11 @@ public class NotificationPanelView extends PanelView implements if (mQsExpandImmediate) { mNotificationStackScroller.setVisibility(View.GONE); mQsFrame.setVisibility(View.VISIBLE); + mHomeControlsLayout.setVisibility(View.VISIBLE); } else { mNotificationStackScroller.setVisibility(View.VISIBLE); mQsFrame.setVisibility(View.GONE); + mHomeControlsLayout.setVisibility(View.GONE); } } return false; @@ -1551,6 +1574,7 @@ public class NotificationPanelView extends PanelView implements if (mKeyguardShowing && isQsSplitEnabled()) { mNotificationStackScroller.setVisibility(View.VISIBLE); mQsFrame.setVisibility(View.VISIBLE); + mHomeControlsLayout.setVisibility(View.GONE); } if (oldState == StatusBarState.KEYGUARD @@ -2099,8 +2123,10 @@ public class NotificationPanelView extends PanelView implements t = (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded - panelHeightQsCollapsed); } - setQsExpansion(mQsMinExpansionHeight - + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight)); + float targetHeight = mQsMinExpansionHeight + + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight); + setQsExpansion(targetHeight); + mHomeControlsLayout.setTranslationY(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); 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 3508c90bc8a2..ccb85fa64364 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -221,7 +221,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBiometricUnlockController = biometricUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry, - mExpansionCallback, falsingManager); + mExpansionCallback, falsingManager, bypassController); mNotificationPanelView = notificationPanelView; notificationPanelView.addExpansionListener(this); mBypassController = bypassController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java index 0d6178b1176b..f2c0434a1a95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java @@ -97,7 +97,7 @@ public class EmergencyCryptkeeperText extends TextView { boolean allSimsMissing = true; CharSequence displayText = null; - List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); + List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); final int N = subs.size(); for (int i = 0; i < N; i++) { int subId = subs.get(i).getSubscriptionId(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java index 0044ca7c0409..1ae1b9779f42 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java @@ -70,15 +70,10 @@ public class CarrierTextControllerTest extends SysuiTestCase { private static final CharSequence AIRPLANE_MODE_TEXT = "Airplane mode"; private static final String TEST_CARRIER = "TEST_CARRIER"; private static final String TEST_CARRIER_2 = "TEST_CARRIER_2"; - private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24"; private static final int TEST_CARRIER_ID = 1; private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", - DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, - TEST_CARRIER_ID, 0); - private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(0, "", 0, - TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", - DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID, + DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null, TEST_CARRIER_ID, 0); private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", @@ -125,7 +120,6 @@ public class CarrierTextControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor); // This should not start listening on any of the real dependencies mCarrierTextController.setListening(mCarrierTextCallback); - mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(false); } @Test @@ -134,7 +128,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { reset(mCarrierTextCallback); List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -154,7 +148,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { reset(mCarrierTextCallback); List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY); when(mKeyguardUpdateMonitor.getSimState(1)).thenReturn( IccCardConstants.State.CARD_IO_ERROR); @@ -178,7 +172,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Test public void testWrongSlots() { reset(mCarrierTextCallback); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn( + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn( new ArrayList<>()); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( IccCardConstants.State.CARD_IO_ERROR); @@ -192,7 +186,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Test public void testMoreSlotsThanSubs() { reset(mCarrierTextCallback); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn( + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn( new ArrayList<>()); // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the @@ -242,7 +236,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -266,7 +260,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION_ROAMING); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -287,7 +281,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Test public void testCreateInfo_noSubscriptions() { reset(mCarrierTextCallback); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn( + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn( new ArrayList<>()); ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = @@ -311,7 +305,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { list.add(TEST_SUBSCRIPTION); list.add(TEST_SUBSCRIPTION); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -336,7 +330,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())) .thenReturn(IccCardConstants.State.READY) .thenReturn(IccCardConstants.State.NOT_READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -361,7 +355,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())) .thenReturn(IccCardConstants.State.NOT_READY) .thenReturn(IccCardConstants.State.READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); @@ -388,7 +382,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { .thenReturn(IccCardConstants.State.READY) .thenReturn(IccCardConstants.State.NOT_READY) .thenReturn(IccCardConstants.State.READY); - when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = @@ -403,30 +397,6 @@ public class CarrierTextControllerTest extends SysuiTestCase { captor.getValue().carrierText); } - @Test - public void testCarrierText_GroupedSubWithOpportunisticCarrierText() { - reset(mCarrierTextCallback); - List<SubscriptionInfo> list = new ArrayList<>(); - list.add(TEST_SUBSCRIPTION); - list.add(TEST_SUBSCRIPTION_2); - when(mKeyguardUpdateMonitor.getSimState(anyInt())) - .thenReturn(IccCardConstants.State.READY); - - mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(true); - when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list); - - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = - ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); - - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); - verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); - - assertEquals(TEST_CARRIER_2, captor.getValue().carrierText); - } - public static class TestCarrierTextController extends CarrierTextController { private KeyguardUpdateMonitor mKUM; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index db6177a63f96..a3cb6c05ca7a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -16,15 +16,18 @@ package com.android.keyguard; +import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; +import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,6 +45,7 @@ import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.os.UserManager; import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -62,6 +66,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @SmallTest @@ -73,7 +79,18 @@ import java.util.concurrent.atomic.AtomicBoolean; // new tests. @RunWithLooper(setAsMainLooper = true) public class KeyguardUpdateMonitorTest extends SysuiTestCase { - + private static final String TEST_CARRIER = "TEST_CARRIER"; + private static final String TEST_CARRIER_2 = "TEST_CARRIER_2"; + private static final int TEST_CARRIER_ID = 1; + private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24"; + private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0, + TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, + TEST_CARRIER_ID, 0); + private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0, + TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID, + TEST_CARRIER_ID, 0); @Mock private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker; @Mock @@ -92,6 +109,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private DevicePolicyManager mDevicePolicyManager; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock + private SubscriptionManager mSubscriptionManager; private TestableLooper mTestableLooper; private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -119,6 +138,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { context.addMockSystemService(FaceManager.class, mFaceManager); context.addMockSystemService(UserManager.class, mUserManager); context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); + context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager); mTestableLooper = TestableLooper.get(this); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context); @@ -441,6 +461,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } + @Test + public void testGetSubscriptionInfo_whenInGroupedSubWithOpportunistic() { + List<SubscriptionInfo> list = new ArrayList<>(); + list.add(TEST_SUBSCRIPTION); + list.add(TEST_SUBSCRIPTION_2); + when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list); + mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged( + TEST_SUBSCRIPTION_2.getSubscriptionId()); + mTestableLooper.processAllMessages(); + + List<SubscriptionInfo> listToVerify = mKeyguardUpdateMonitor + .getFilteredSubscriptionInfo(false); + assertThat(listToVerify.size()).isEqualTo(1); + assertThat(listToVerify.get(0)).isEqualTo(TEST_SUBSCRIPTION_2); + } + private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE; diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java deleted file mode 100644 index 7ea6493da83d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 com.android.systemui.classifier; - -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertThat; - -import android.os.Handler; -import android.provider.DeviceConfig; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.classifier.brightline.BrightLineFalsingManager; -import com.android.systemui.shared.plugins.PluginManager; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class FalsingManagerProxyTest extends SysuiTestCase { - @Mock - PluginManager mPluginManager; - private boolean mDefaultConfigValue; - private Handler mHandler; - private TestableLooper mTestableLooper; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); - mHandler = new Handler(mTestableLooper.getLooper()); - mDefaultConfigValue = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, false); - // In case it runs on a device where it's been set to true, set it to false by hand. - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false); - } - - @After - public void tearDown() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, mDefaultConfigValue ? "true" : "false", false); - } - - @Test - public void test_brightLineFalsingManagerDisabled() { - FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler); - - assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class)); - } - - @Test - public void test_brightLineFalsingManagerEnabled() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false); - FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler); - - assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class)); - } - - @Test - public void test_brightLineFalsingManagerToggled() { - FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler); - assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class)); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false); - mTestableLooper.processAllMessages(); - proxy.setupFalsingManager(getContext()); - assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class)); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false); - mTestableLooper.processAllMessages(); - proxy.setupFalsingManager(getContext()); - assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java new file mode 100644 index 000000000000..b4a60d642cb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java @@ -0,0 +1,67 @@ +/* + * 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 com.android.systemui.glwallpaper; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.RETURNS_DEFAULTS; +import static org.mockito.Mockito.mock; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceHolder; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class EglHelperTest extends SysuiTestCase { + + @Mock + private EglHelper mEglHelper; + @Mock + private SurfaceHolder mSurfaceHolder; + + @Before + public void setUp() throws Exception { + mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS); + mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS); + } + + @Test + public void testInit_finish() { + mEglHelper.init(mSurfaceHolder); + mEglHelper.finish(); + } + + @Test + public void testFinish_shouldNotCrash() { + assertFalse(mEglHelper.hasEglDisplay()); + assertFalse(mEglHelper.hasEglSurface()); + assertFalse(mEglHelper.hasEglContext()); + + mEglHelper.finish(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index 907e695f2513..cd60e47eef50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; @@ -84,6 +85,8 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Mock private UnlockMethodCache mUnlockMethodCache; @Mock + private KeyguardBypassController mKeyguardBypassController; + @Mock private Handler mHandler; private KeyguardBouncer mBouncer; @@ -98,7 +101,8 @@ public class KeyguardBouncerTest extends SysuiTestCase { when(mKeyguardHostView.getHeight()).thenReturn(500); mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback, mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager, - mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor, mHandler) { + mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor, + mKeyguardBypassController, mHandler) { @Override protected void inflateView() { super.inflateView(); @@ -391,6 +395,15 @@ public class KeyguardBouncerTest extends SysuiTestCase { } @Test + public void testShow_delaysIfFaceAuthIsRunning_unlessBypass() { + when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true); + when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true); + mBouncer.show(true /* reset */); + + verify(mHandler, never()).postDelayed(any(), anyLong()); + } + + @Test public void testRegisterUpdateMonitorCallback() { verify(mKeyguardUpdateMonitor).registerCallback(any()); } diff --git a/packages/services/PacProcessor/Android.bp b/packages/services/PacProcessor/Android.bp index 93b2d956cf6f..494a8187886d 100644 --- a/packages/services/PacProcessor/Android.bp +++ b/packages/services/PacProcessor/Android.bp @@ -21,3 +21,9 @@ android_app { certificate: "platform", jni_libs: ["libjni_pacprocessor"], } + +filegroup { + name: "PacProcessor-aidl-sources", + srcs: ["src/**/*.aidl"], + path: "src", +} diff --git a/packages/services/PacProcessor/com/android/net/IProxyService.aidl b/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl index 4e54aba5c3bf..4e54aba5c3bf 100644 --- a/packages/services/PacProcessor/com/android/net/IProxyService.aidl +++ b/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl diff --git a/packages/services/Proxy/Android.bp b/packages/services/Proxy/Android.bp index 87aa7637df8e..d93c9f8d9941 100644 --- a/packages/services/Proxy/Android.bp +++ b/packages/services/Proxy/Android.bp @@ -5,3 +5,9 @@ android_app { certificate: "platform", privileged: true, } + +filegroup { + name: "ProxyHandler-aidl-sources", + srcs: ["src/**/*.aidl"], + path: "src", +} diff --git a/packages/services/Proxy/com/android/net/IProxyCallback.aidl b/packages/services/Proxy/src/com/android/net/IProxyCallback.aidl index 26b2a3f9da2d..26b2a3f9da2d 100644 --- a/packages/services/Proxy/com/android/net/IProxyCallback.aidl +++ b/packages/services/Proxy/src/com/android/net/IProxyCallback.aidl diff --git a/packages/services/Proxy/com/android/net/IProxyPortListener.aidl b/packages/services/Proxy/src/com/android/net/IProxyPortListener.aidl index fa4caf3dba91..fa4caf3dba91 100644 --- a/packages/services/Proxy/com/android/net/IProxyPortListener.aidl +++ b/packages/services/Proxy/src/com/android/net/IProxyPortListener.aidl diff --git a/services/Android.bp b/services/Android.bp index 54157d0b7eb1..27f8d36894da 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -43,6 +43,10 @@ java_library { "android.hidl.manager-V1.0-java", ], + plugins: [ + "compat-changeid-annotation-processor", + ], + // Uncomment to enable output of certain warnings (deprecated, unchecked) //javacflags: ["-Xlint"], @@ -56,3 +60,9 @@ cc_library_shared { defaults: ["libservices.core-libs"], whole_static_libs: ["libservices.core"], } + +platform_compat_config { + name: "services-platform-compat-config", + prefix: "services", + src: ":services", +} diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 135615708037..1d936f263cb8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -348,13 +348,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - protected abstract boolean isCalledForCurrentUserLocked(); + protected abstract boolean hasRightsToCurrentUserLocked(); @Override public List<AccessibilityWindowInfo> getWindows() { ensureWindowsAvailableTimed(); synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } final boolean permissionGranted = @@ -362,8 +362,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ if (!permissionGranted) { return null; } + // TODO [Multi-Display] (b/134891479) : + // using correct display Id to replace DEFAULT_DISPLAY. List<AccessibilityWindowInfo> internalWindowList = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); if (internalWindowList == null) { return null; } @@ -387,7 +389,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public AccessibilityWindowInfo getWindow(int windowId) { ensureWindowsAvailableTimed(); synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } final boolean permissionGranted = @@ -420,7 +422,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -481,7 +483,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -542,7 +544,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -602,7 +604,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked( @@ -663,7 +665,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -728,7 +730,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ throws RemoteException { final int resolvedWindowId; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -748,7 +750,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean performGlobalAction(int action) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } } @@ -771,7 +773,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationScale(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 1.0f; } } @@ -787,7 +789,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public Region getMagnificationRegion(int displayId) { synchronized (mLock) { final Region region = Region.obtain(); - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return region; } MagnificationController magnificationController = @@ -810,7 +812,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterX(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 0.0f; } MagnificationController magnificationController = @@ -832,7 +834,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterY(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 0.0f; } MagnificationController magnificationController = @@ -864,7 +866,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean resetMagnification(int displayId, boolean animate) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } if (!mSecurityPolicy.canControlMagnification(this)) { @@ -886,7 +888,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } if (!mSecurityPolicy.canControlMagnification(this)) { @@ -1309,23 +1311,25 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ private void ensureWindowsAvailableTimed() { synchronized (mLock) { - if (mA11yWindowManager.getWindowListLocked() != null) { + // TODO [Multi-Display] (b/134891479) : + // using correct display Id to replace DEFAULT_DISPLAY. + if (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) != null) { return; } // If we have no registered callback, update the state we // we may have to register one but it didn't happen yet. - if (!mA11yWindowManager.isTrackingWindowsLocked()) { + if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) { // Invokes client change to make sure tracking window enabled. mSystemSupport.onClientChangeLocked(false); } // We have no windows but do not care about them, done. - if (!mA11yWindowManager.isTrackingWindowsLocked()) { + if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) { return; } // Wait for the windows with a timeout. final long startMillis = SystemClock.uptimeMillis(); - while (mA11yWindowManager.getWindowListLocked() == null) { + while (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) == null) { final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis; if (remainMillis <= 0) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 893e4e49020b..ddf5bbec7eb1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -272,9 +272,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); mMainHandler = new MainHandler(mContext.getMainLooper()); mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService); - mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); + mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); registerBroadcastReceivers(); @@ -580,9 +580,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Make sure clients receiving this event will be able to get the // current state of the windows as the window manager may be delaying // the computation for performance reasons. - // TODO [Multi-Display] : using correct display Id to replace DEFAULT_DISPLAY - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - && mA11yWindowManager.isTrackingWindowsLocked()) { + // TODO [Multi-Display] : using correct display Id to replace DEFAULT_DISPLAY. + boolean shouldComputeWindows = false; + synchronized (mLock) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + && mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) { + shouldComputeWindows = true; + } + } + if (shouldComputeWindows) { WindowManagerInternal wm = LocalServices.getService(WindowManagerInternal.class); wm.computeWindowsForAccessibility(Display.DEFAULT_DISPLAY); } @@ -1656,10 +1662,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - if (observingWindows) { - mA11yWindowManager.startTrackingWindows(); - } else { - mA11yWindowManager.stopTrackingWindows(); + // Gets all valid displays and start tracking windows of each display if there is at least + // one bound service that can retrieve window content. + final ArrayList<Display> displays = getValidDisplayList(); + for (int i = 0; i < displays.size(); i++) { + final Display display = displays.get(i); + if (display != null) { + if (observingWindows) { + mA11yWindowManager.startTrackingWindows(display.getDisplayId()); + } else { + mA11yWindowManager.stopTrackingWindows(display.getDisplayId()); + } + } } } @@ -2559,6 +2573,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } updateMagnificationLocked(userState); + updateWindowsForAccessibilityCallbackLocked(userState); } } @@ -2586,6 +2601,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (mMagnificationController != null) { mMagnificationController.onDisplayRemoved(displayId); } + mA11yWindowManager.stopTrackingWindows(displayId); } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 315d6fa287f2..203210998c48 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -468,7 +468,13 @@ public class AccessibilitySecurityPolicy { } } - private boolean hasPermission(String permission) { + /** + * Permission check to caller. + * + * @param permission The permission to check + * @return true if caller has permission + */ + public boolean hasPermission(@NonNull String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 02f7821bd0d5..d7f61e5371d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -18,6 +18,7 @@ package com.android.server.accessibility; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; @@ -27,6 +28,7 @@ import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; @@ -211,19 +213,31 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below // performs the current profile parent resolution. - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT); - return resolvedUserId == mSystemSupport.getCurrentUserIdLocked(); + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.ROOT_UID + || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return true; + } + if (mSecurityPolicy.resolveProfileParentLocked(UserHandle.getUserId(callingUid)) + == mSystemSupport.getCurrentUserIdLocked()) { + return true; + } + if (mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS) + || mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { + return true; + } + return false; } @Override public boolean setSoftKeyboardShowMode(int showMode) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } final UserState userState = mUserStateWeakReference.get(); @@ -241,7 +255,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } UserState userState = mUserStateWeakReference.get(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index c9efe36b2014..82a593cdfd7f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -79,16 +79,25 @@ public class AccessibilityWindowManager { private final SparseArray<SparseArray<IBinder>> mWindowTokens = new SparseArray<>(); private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection; - + // There is only one active window in the system. It is updated when the top focused window + // of the top focused display changes and when we receive a TYPE_WINDOW_STATE_CHANGED event. private int mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; - private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + // There is only one top focused window in the system. It is updated when the window manager + // updates the window lists. + private int mTopFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; private int mAccessibilityFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; private long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; + // The top focused display and window token updated with the callback of window lists change. + private int mTopFocusedDisplayId; + private IBinder mTopFocusedWindowToken; + // The display has the accessibility focused window currently. + private int mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY; private boolean mTouchInteractionInProgress; - // TO-DO [Multi-Display] : make DisplayWindowObserver to plural - private DisplayWindowsObserver mDisplayWindowsObserver; + /** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */ + private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers = + new SparseArray<>(); /** * This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to @@ -243,6 +252,7 @@ public class AccessibilityWindowManager { for (int i = 0; i < windowCount; i++) { AccessibilityWindowInfo window = mWindows.get(i); if (window.getId() == windowId) { + mAccessibilityFocusedDisplayId = mDisplayId; window.setAccessibilityFocused(true); mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked( AccessibilityEvent.obtainWindowsChangedEvent( @@ -318,17 +328,21 @@ public class AccessibilityWindowManager { * Callbacks from window manager when there's an accessibility change in windows. * * @param forceSend Send the windows for accessibility even if they haven't changed. + * @param topFocusedDisplayId The display Id which has the top focused window. + * @param topFocusedWindowToken The window token of top focused window. * @param windows The windows for accessibility. */ @Override - public void onWindowsForAccessibilityChanged(boolean forceSend, - @NonNull List<WindowInfo> windows) { + public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId, + IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) { synchronized (mLock) { if (DEBUG) { Slog.i(LOG_TAG, "Display Id = " + mDisplayId); Slog.i(LOG_TAG, "Windows changed: " + windows); } if (shouldUpdateWindowsLocked(forceSend, windows)) { + mTopFocusedDisplayId = topFocusedDisplayId; + mTopFocusedWindowToken = topFocusedWindowToken; cacheWindows(windows); // Lets the policy update the focused and active windows. updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), @@ -471,6 +485,7 @@ public class AccessibilityWindowManager { final List<AccessibilityWindowInfo> oldWindowList = new ArrayList<>(mWindows); final SparseArray<AccessibilityWindowInfo> oldWindowsById = mA11yWindowInfoById.clone(); + boolean shouldClearAccessibilityFocus = false; mWindows.clear(); mA11yWindowInfoById.clear(); @@ -480,9 +495,25 @@ public class AccessibilityWindowManager { } mWindowInfoById.clear(); mHasWatchOutsideTouchWindow = false; - mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; - if (!mTouchInteractionInProgress) { - mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + + final int windowCount = windows.size(); + final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId; + final boolean isAccessibilityFocusedDisplay = + mDisplayId == mAccessibilityFocusedDisplayId; + // Modifies the value of top focused window, active window and a11y focused window + // only if this display is top focused display which has the top focused window. + if (isTopFocusedDisplay) { + if (windowCount > 0) { + // Sets the top focus window by top focused window token. + mTopFocusedWindowId = findWindowIdLocked(userId, mTopFocusedWindowToken); + } else { + // Resets the top focus window when stopping tracking window of this display. + mTopFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + // The active window doesn't need to be reset if the touch operation is progressing. + if (!mTouchInteractionInProgress) { + mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } } // If the active window goes away while the user is touch exploring we @@ -492,17 +523,17 @@ public class AccessibilityWindowManager { // filters out such events. boolean activeWindowGone = true; - final int windowCount = windows.size(); - // We'll clear accessibility focus if the window with focus is no longer visible to - // accessibility services - boolean shouldClearAccessibilityFocus = - mAccessibilityFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + // accessibility services. + if (isAccessibilityFocusedDisplay) { + shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId + != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } if (windowCount > 0) { for (int i = 0; i < windowCount; i++) { final WindowInfo windowInfo = windows.get(i); final AccessibilityWindowInfo window; - if (isTrackingWindowsLocked()) { + if (mTrackingWindows) { window = populateReportedWindowLocked(userId, windowInfo); } else { window = null; @@ -513,9 +544,10 @@ public class AccessibilityWindowManager { window.setLayer(windowCount - 1 - window.getLayer()); final int windowId = window.getId(); - if (window.isFocused()) { - mFocusedWindowId = windowId; + if (window.isFocused() && isTopFocusedDisplay) { if (!mTouchInteractionInProgress) { + // This display is top one, and sets the focus window + // as active window. mActiveWindowId = windowId; window.setActive(true); } else if (windowId == mActiveWindowId) { @@ -530,22 +562,28 @@ public class AccessibilityWindowManager { mWindowInfoById.put(windowId, WindowInfo.obtain(windowInfo)); } } - - if (mTouchInteractionInProgress && activeWindowGone) { - mActiveWindowId = mFocusedWindowId; - } - - // Focused window may change the active one, so set the - // active window once we decided which it is. final int accessibilityWindowCount = mWindows.size(); - for (int i = 0; i < accessibilityWindowCount; i++) { - final AccessibilityWindowInfo window = mWindows.get(i); - if (window.getId() == mActiveWindowId) { - window.setActive(true); + if (isTopFocusedDisplay) { + if (mTouchInteractionInProgress && activeWindowGone) { + mActiveWindowId = mTopFocusedWindowId; } - if (window.getId() == mAccessibilityFocusedWindowId) { - window.setAccessibilityFocused(true); - shouldClearAccessibilityFocus = false; + // Focused window may change the active one, so set the + // active window once we decided which it is. + for (int i = 0; i < accessibilityWindowCount; i++) { + final AccessibilityWindowInfo window = mWindows.get(i); + if (window.getId() == mActiveWindowId) { + window.setActive(true); + } + } + } + if (isAccessibilityFocusedDisplay) { + for (int i = 0; i < accessibilityWindowCount; i++) { + final AccessibilityWindowInfo window = mWindows.get(i); + if (window.getId() == mAccessibilityFocusedWindowId) { + window.setAccessibilityFocused(true); + shouldClearAccessibilityFocus = false; + break; + } } } } @@ -788,42 +826,86 @@ public class AccessibilityWindowManager { mAccessibilityEventSender = accessibilityEventSender; mSecurityPolicy = securityPolicy; mAccessibilityUserManager = accessibilityUserManager; - mDisplayWindowsObserver = new DisplayWindowsObserver(Display.DEFAULT_DISPLAY); } /** - * Starts tracking windows changes from window manager. + * Starts tracking windows changes from window manager for specified display. + * + * @param displayId The logical display id. */ - public void startTrackingWindows() { + public void startTrackingWindows(int displayId) { synchronized (mLock) { - mDisplayWindowsObserver.startTrackingWindowsLocked(); + DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + if (observer == null) { + observer = new DisplayWindowsObserver(displayId); + } + if (observer.isTrackingWindowsLocked()) { + return; + } + if (observer.startTrackingWindowsLocked()) { + mDisplayWindowsObservers.put(displayId, observer); + } } } /** - * Stops tracking windows changes from window manager, and clear all windows info. + * Stops tracking windows changes from window manager, and clear all windows info for specified + * display. + * + * @param displayId The logical display id. */ - public void stopTrackingWindows() { + public void stopTrackingWindows(int displayId) { synchronized (mLock) { - mDisplayWindowsObserver.stopTrackingWindowsLocked(); + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + if (observer != null) { + observer.stopTrackingWindowsLocked(); + mDisplayWindowsObservers.remove(displayId); + } } } /** - * Returns true if windows changes tracking. + * Checks if we are tracking windows on any display. * - * @return true if windows changes tracking + * @return {@code true} if the observer is tracking windows on any display, + * {@code false} otherwise. */ public boolean isTrackingWindowsLocked() { - return mDisplayWindowsObserver.isTrackingWindowsLocked(); + final int count = mDisplayWindowsObservers.size(); + if (count > 0) { + return true; + } + return false; } /** - * Returns accessibility windows. + * Checks if we are tracking windows on specified display. + * + * @param displayId The logical display id. + * @return {@code true} if the observer is tracking windows on specified display, + * {@code false} otherwise. + */ + public boolean isTrackingWindowsLocked(int displayId) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + if (observer != null) { + return observer.isTrackingWindowsLocked(); + } + return false; + } + + /** + * Returns accessibility windows for specified display. + * + * @param displayId The logical display id. + * @return accessibility windows for specified display. */ @Nullable - public List<AccessibilityWindowInfo> getWindowListLocked() { - return mDisplayWindowsObserver.getWindowListLocked(); + public List<AccessibilityWindowInfo> getWindowListLocked(int displayId) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + if (observer != null) { + return observer.getWindowListLocked(); + } + return null; } /** @@ -841,6 +923,8 @@ public class AccessibilityWindowManager { @NonNull IAccessibilityInteractionConnection connection, @NonNull String packageName, int userId) throws RemoteException { final int windowId; + boolean shouldComputeWindows = false; + final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken.asBinder()); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -880,9 +964,14 @@ public class AccessibilityWindowManager { + " and token: " + windowToken.asBinder()); } } + + if (isTrackingWindowsLocked(displayId)) { + shouldComputeWindows = true; + } + } + if (shouldComputeWindows) { + mWindowManagerInternal.computeWindowsForAccessibility(displayId); } - // TODO [Multi-Display] : using correct display Id to replace DEFAULT_DISPLAY - mWindowManagerInternal.computeWindowsForAccessibility(Display.DEFAULT_DISPLAY); return windowId; } @@ -1067,8 +1156,12 @@ public class AccessibilityWindowManager { */ public boolean computePartialInteractiveRegionForWindowLocked(int windowId, @NonNull Region outRegion) { - return mDisplayWindowsObserver.computePartialInteractiveRegionForWindowLocked(windowId, - outRegion); + final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); + if (observer != null) { + return observer.computePartialInteractiveRegionForWindowLocked(windowId, outRegion); + } + + return false; } /** @@ -1101,8 +1194,8 @@ public class AccessibilityWindowManager { // windows are delivered. synchronized (mLock) { if (!isTrackingWindowsLocked()) { - mFocusedWindowId = findFocusedWindowId(userId); - if (windowId == mFocusedWindowId) { + mTopFocusedWindowId = findFocusedWindowId(userId); + if (windowId == mTopFocusedWindowId) { mActiveWindowId = windowId; } } @@ -1140,6 +1233,7 @@ public class AccessibilityWindowManager { && (mAccessibilityFocusedWindowId == windowId) && (eventAction != AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)) { mAccessibilityFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY; } } } break; @@ -1172,7 +1266,7 @@ public class AccessibilityWindowManager { // the active window before all hover accessibility events from // the touched window are delivered is fine. final int oldActiveWindow = mActiveWindowId; - setActiveWindowLocked(mFocusedWindowId); + setActiveWindowLocked(mTopFocusedWindowId); // If there is no service that can operate with interactive windows // then we keep the old behavior where a window loses accessibility @@ -1212,7 +1306,14 @@ public class AccessibilityWindowManager { mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)); mActiveWindowId = windowId; - mDisplayWindowsObserver.setActiveWindowLocked(windowId); + // Goes through all windows for each display. + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + observer.setActiveWindowLocked(windowId); + } + } } } @@ -1224,7 +1325,14 @@ public class AccessibilityWindowManager { WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); mAccessibilityFocusedWindowId = windowId; - mDisplayWindowsObserver.setAccessibilityFocusedWindowLocked(windowId); + // Goes through all windows for each display. + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + observer.setAccessibilityFocusedWindowLocked(windowId); + } + } } } @@ -1236,7 +1344,11 @@ public class AccessibilityWindowManager { */ @Nullable public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) { - return mDisplayWindowsObserver.findA11yWindowInfoByIdLocked(windowId); + final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); + if (observer != null) { + return observer.findA11yWindowInfoByIdLocked(windowId); + } + return null; } /** @@ -1247,7 +1359,11 @@ public class AccessibilityWindowManager { */ @Nullable public WindowInfo findWindowInfoByIdLocked(int windowId) { - return mDisplayWindowsObserver.findWindowInfoByIdLocked(windowId); + final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); + if (observer != null) { + return observer.findWindowInfoByIdLocked(windowId); + } + return null; } /** @@ -1259,7 +1375,7 @@ public class AccessibilityWindowManager { */ public int getFocusedWindowId(int focusType) { if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) { - return mFocusedWindowId; + return mTopFocusedWindowId; } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) { return mAccessibilityFocusedWindowId; } @@ -1273,7 +1389,17 @@ public class AccessibilityWindowManager { */ @Nullable public AccessibilityWindowInfo getPictureInPictureWindowLocked() { - return mDisplayWindowsObserver.getPictureInPictureWindowLocked(); + AccessibilityWindowInfo windowInfo = null; + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + if ((windowInfo = observer.getPictureInPictureWindowLocked()) != null) { + break; + } + } + } + return windowInfo; } /** @@ -1313,10 +1439,13 @@ public class AccessibilityWindowManager { final List<Integer> outsideWindowsIds; final List<RemoteAccessibilityConnection> connectionList = new ArrayList<>(); synchronized (mLock) { - outsideWindowsIds = - mDisplayWindowsObserver.getWatchOutsideTouchWindowIdLocked(targetWindowId); - for (int i = 0; i < outsideWindowsIds.size(); i++) { - connectionList.add(getConnectionLocked(userId, outsideWindowsIds.get(i))); + final DisplayWindowsObserver observer = + getDisplayWindowObserverByWindowIdLocked(targetWindowId); + if (observer != null) { + outsideWindowsIds = observer.getWatchOutsideTouchWindowIdLocked(targetWindowId); + for (int i = 0; i < outsideWindowsIds.size(); i++) { + connectionList.add(getConnectionLocked(userId, outsideWindowsIds.get(i))); + } } } for (int i = 0; i < connectionList.size(); i++) { @@ -1398,10 +1527,29 @@ public class AccessibilityWindowManager { } } + private DisplayWindowsObserver getDisplayWindowObserverByWindowIdLocked(int windowId) { + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + if (observer.findWindowInfoByIdLocked(windowId) != null) { + return mDisplayWindowsObservers.get(observer.mDisplayId); + } + } + } + return null; + } + /** * Dumps all {@link AccessibilityWindowInfo}s here. */ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { - mDisplayWindowsObserver.dumpLocked(fd, pw, args); + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + observer.dumpLocked(fd, pw, args); + } + } } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 2698b72fdb36..79d975dac2b2 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -66,6 +66,7 @@ class UiAutomationManager { mUiAutomationServiceOwner.unlinkToDeath(this, 0); mUiAutomationServiceOwner = null; destroyUiAutomationService(); + Slog.v(LOG_TAG, "UiAutomation service owner died"); } }; @@ -263,7 +264,7 @@ class UiAutomationManager { } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { // Allow UiAutomation to work for any user return true; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index a64f4e475b7d..6b7c3e69e54e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -807,6 +807,7 @@ public final class AutofillManagerService packageName, versionCode, userId); final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled); mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName); + injectDisableAppInfo(options, userId, packageName); return options; } @@ -820,6 +821,19 @@ public final class AutofillManagerService } return false; } + + private void injectDisableAppInfo(@NonNull AutofillOptions options, int userId, + String packageName) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); + if (service != null) { + options.appDisabledExpiration = service.getAppDisabledExpirationLocked( + packageName); + options.disabledActivities = service.getAppDisabledActivitiesLocked( + packageName); + } + } + } } /** diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 1e1e07d32588..d7ed2e9abde4 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -980,12 +980,12 @@ final class AutofillManagerServiceImpl for (int i = 0; i < size; i++) { final String packageName = mDisabledApps.keyAt(i); final long expiration = mDisabledApps.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(packageName).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(packageName).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); } pw.print(prefix); pw.print("Disabled activities: "); @@ -1000,12 +1000,12 @@ final class AutofillManagerServiceImpl for (int i = 0; i < size; i++) { final ComponentName component = mDisabledActivities.keyAt(i); final long expiration = mDisabledActivities.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(component).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(component).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); } final int size = mSessions.size(); @@ -1418,6 +1418,36 @@ final class AutofillManagerServiceImpl } } + // Called by AutofillManagerService + long getAppDisabledExpirationLocked(@NonNull String packageName) { + if (mDisabledApps == null) { + return 0; + } + final Long expiration = mDisabledApps.get(packageName); + return expiration != null ? expiration : 0; + } + + // Called by AutofillManagerService + @Nullable + ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { + if (mDisabledActivities != null) { + final int size = mDisabledActivities.size(); + ArrayMap<String, Long> disabledList = null; + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + if (packageName.equals(component.getPackageName())) { + if (disabledList == null) { + disabledList = new ArrayMap<>(); + } + final long expiration = mDisabledActivities.valueAt(i); + disabledList.put(component.flattenToShortString(), expiration); + } + } + return disabledList; + } + return null; + } + /** * Checks if autofill is disabled by service to the given activity. */ diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 4579a3d5cf9f..48f16acc864a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -772,13 +772,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final long disableDuration = response.getDisableDuration(); if (disableDuration > 0) { final int flags = response.getFlags(); - if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) { + final boolean disableActivityOnly = + (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0; + notifyDisableAutofillToClient(disableDuration, + disableActivityOnly ? mComponentName : null); + + if (disableActivityOnly) { mService.disableAutofillForActivity(mComponentName, disableDuration, id, mCompatMode); } else { mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, id, mCompatMode); } + // Although "standard" autofill is disabled, it might still trigger augmented autofill if (triggerAugmentedAutofillLocked() != null) { mForAugmentedAutofillOnly = true; @@ -2567,6 +2573,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + private void notifyDisableAutofillToClient(long disableDuration, ComponentName componentName) { + synchronized (mLock) { + if (mCurrentViewId == null) return; + try { + mClient.notifyDisableAutofill(disableDuration, componentName); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying client disable autofill: id=" + mCurrentViewId, e); + } + } + } + @GuardedBy("mLock") private void updateTrackedIdsLocked() { // Only track the views of the last response as only those are reported back to the diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index e53141229e91..18009e191482 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -25,6 +25,7 @@ import android.gsi.IGsid; import android.os.Environment; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -195,7 +196,20 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements } @Override - public boolean write(byte[] buf) throws RemoteException { - return getGsiService().commitGsiChunkFromMemory(buf); + public boolean setAshmem(ParcelFileDescriptor ashmem, long size) { + try { + return getGsiService().setGsiAshmem(ashmem, size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + @Override + public boolean submitFromAshmem(long size) { + try { + return getGsiService().commitGsiChunkFromAshmem(size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } } } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 4facf4ea62a2..4151c7269934 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -54,6 +54,10 @@ import com.android.server.pm.UserManagerService; * {@hide} */ public abstract class SystemService { + + // TODO(b/133242016) STOPSHIP: change to false before R ships + protected static final boolean DEBUG_USER = true; + /* * Boot Phases */ @@ -150,7 +154,17 @@ public abstract class SystemService { public void onBootPhase(int phase) {} /** - * @deprecated subclasses should extend {@link #onStartUser(int, int)} instead (which by default + * Checks if the service should be available for the given user. + * + * <p>By default returns {@code true}, but subclasses should extend for optimization, if they + * don't support some types (like headless system user). + */ + public boolean isSupported(@NonNull UserInfo userInfo) { + return true; + } + + /** + * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default * calls this method). */ @Deprecated @@ -160,6 +174,9 @@ public abstract class SystemService { * Called when a new user is starting, for system services to initialize any per-user * state they maintain for running users. * + * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this + * user. + * * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object * referenced by {@link UserManagerService} and hence should not be modified. */ @@ -168,7 +185,7 @@ public abstract class SystemService { } /** - * @deprecated subclasses should extend {@link #onUnlockUser(int, int)} instead (which by + * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by * default calls this method). */ @Deprecated @@ -185,6 +202,9 @@ public abstract class SystemService { * Code written inside system services should use * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of * these states. + * <p> + * This method is only called when the service {@link #isSupported(UserInfo) supports} this + * user. * * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object * referenced by {@link UserManagerService} and hence should not be modified. @@ -194,8 +214,8 @@ public abstract class SystemService { } /** - * @deprecated subclasses should extend {@link #onSwitchUser(int, int)} instead (which by - * default calls this method). + * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead + * (which by default calls this method). */ @Deprecated public void onSwitchUser(@UserIdInt int userHandle) {} @@ -205,15 +225,21 @@ public abstract class SystemService { * special behavior for whichever user is currently in the foreground. This is called * before any application processes are aware of the new user. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object + * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either + * of the users ({@code from} or {@code to}). + * + * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects * referenced by {@link UserManagerService} and hence should not be modified. + * + * @param from The information about the user being switched from. + * @param to The information about the user being switched from to. */ - public void onSwitchUser(@NonNull UserInfo userInfo) { - onSwitchUser(userInfo.id); + public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) { + onSwitchUser(to.id); } /** - * @deprecated subclasses should extend {@link #onStopUser(int, int)} instead (which by default + * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default * calls this method). */ @Deprecated @@ -225,6 +251,9 @@ public abstract class SystemService { * broadcast to the user; it is a good place to stop making use of any resources of that * user (such as binding to a service running in the user). * + * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this + * user. + * * <p>NOTE: This is the last callback where the callee may access the target user's CE storage. * * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object @@ -235,7 +264,7 @@ public abstract class SystemService { } /** - * @deprecated subclasses should extend {@link #onCleanupUser(int, int)} instead (which by + * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by * default calls this method). */ @Deprecated @@ -246,8 +275,12 @@ public abstract class SystemService { * state they maintain for running users. This is called after all application process * teardown of the user is complete. * + * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this + * user. + * * <p>NOTE: When this callback is called, the CE storage for the target user may not be - * accessible already. Use {@link #onCleanupUser} instead if you need to access the CE storage. + * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE + * storage. * * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object * referenced by {@link UserManagerService} and hence should not be modified. diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index ebe23f61cb2a..c7157981ada2 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -23,6 +23,7 @@ import android.content.pm.UserInfo; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.Slog; @@ -41,8 +42,16 @@ import java.util.ArrayList; */ public class SystemServiceManager { private static final String TAG = "SystemServiceManager"; + private static final boolean DEBUG = false; private static final int SERVICE_CALL_WARN_TIME_MS = 50; + // Constants used on onUser(...) + private static final String START = "Start"; + private static final String UNLOCK = "Unlock"; + private static final String SWITCH = "Switch"; + private static final String STOP = "Stop"; + private static final String CLEANUP = "Cleanup"; + private static File sSystemDir; private final Context mContext; private boolean mSafeMode; @@ -90,7 +99,6 @@ public class SystemServiceManager { * @return The service instance, never null. * @throws RuntimeException if the service fails to start. */ - @SuppressWarnings("unchecked") public <T extends SystemService> T startService(Class<T> serviceClass) { try { final String name = serviceClass.getName(); @@ -212,129 +220,107 @@ public class SystemServiceManager { * Starts the given user. */ public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) { - t.traceBegin("ssm.startUser-" + userHandle); - Slog.i(TAG, "Calling onStartUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onStartUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onStartUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting start of user " + userHandle - + " to service " + service.getClass().getName(), ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStartUser "); - t.traceEnd(); - } - t.traceEnd(); + onUser(t, START, userHandle); } /** * Unlocks the given user. */ public void unlockUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.unlockUser-" + userHandle); - Slog.i(TAG, "Calling onUnlockUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onUnlockUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onUnlockUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting unlock of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onUnlockUser "); - t.traceEnd(); - } - t.traceEnd(); + onUser(UNLOCK, userHandle); } /** * Switches to the given user. */ - public void switchUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.switchUser-" + userHandle); - Slog.i(TAG, "Calling switchUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onSwitchUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onSwitchUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting switch of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onSwitchUser"); - t.traceEnd(); - } - t.traceEnd(); + public void switchUser(final @UserIdInt int from, final @UserIdInt int to) { + onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from); } /** * Stops the given user. */ public void stopUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.stopUser-" + userHandle); - Slog.i(TAG, "Calling onStopUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onStopUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onStopUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting stop of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStopUser"); - t.traceEnd(); - } - t.traceEnd(); + onUser(STOP, userHandle); } /** * Cleans up the given user. */ public void cleanupUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.cleanupUser-" + userHandle); - Slog.i(TAG, "Calling onCleanupUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); + onUser(CLEANUP, userHandle); + } + + private void onUser(@NonNull String onWhat, @UserIdInt int userHandle) { + onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle); + } + + private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, + @UserIdInt int userHandle) { + onUser(t, onWhat, userHandle, UserHandle.USER_NULL); + } + + private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, + @UserIdInt int curUserId, @UserIdInt int prevUserId) { + t.traceBegin("ssm." + onWhat + "User-" + curUserId); + Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId); + final UserInfo curUserInfo = getUserInfo(curUserId); + final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null + : getUserInfo(prevUserId); final int serviceLen = mServices.size(); for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); final String serviceName = service.getClass().getName(); - t.traceBegin("onCleanupUser-" + userHandle + " " + serviceName); + boolean supported = service.isSupported(curUserInfo); + + // Must check if either curUser or prevUser is supported (for example, if switching from + // unsupported to supported, we still need to notify the services) + if (!supported && prevUserInfo != null) { + supported = service.isSupported(prevUserInfo); + } + + if (!supported) { + if (DEBUG) { + Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service " + + serviceName + " because it's not supported (curUser: " + + curUserInfo + ", prevUser:" + prevUserInfo + ")"); + } else { + Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on " + + serviceName); + } + continue; + } + t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + " " + serviceName); long time = SystemClock.elapsedRealtime(); try { - service.onCleanupUser(userInfo); + switch (onWhat) { + case SWITCH: + service.onSwitchUser(prevUserInfo, curUserInfo); + break; + case START: + service.onStartUser(curUserInfo); + break; + case UNLOCK: + service.onUnlockUser(curUserInfo); + break; + case STOP: + service.onStopUser(curUserInfo); + break; + case CLEANUP: + service.onCleanupUser(curUserInfo); + break; + default: + throw new IllegalArgumentException(onWhat + " what?"); + } } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle + Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo + " to service " + serviceName, ex); } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onCleanupUser"); - t.traceEnd(); + warnIfTooLong(SystemClock.elapsedRealtime() - time, service, + "on" + onWhat + "User-" + curUserId); + t.traceEnd(); // what on service } - t.traceEnd(); + t.traceEnd(); // main entry } /** Sets the safe mode flag for services to query. */ diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 07482796b027..9936d73fb800 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -1188,6 +1188,7 @@ public class VibratorService extends IVibratorService.Stub private static boolean isNotification(int usageHint) { switch (usageHint) { case AudioAttributes.USAGE_NOTIFICATION: + case AudioAttributes.USAGE_NOTIFICATION_EVENT: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 22928890e75a..5c0fe4e8bcc2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8221,6 +8221,35 @@ public class ActivityManagerService extends IActivityManager.Stub @Deprecated public void requestBugReportWithDescription(@Nullable String shareTitle, @Nullable String shareDescription, int bugreportType) { + String type = null; + switch (bugreportType) { + case ActivityManager.BUGREPORT_OPTION_FULL: + type = "bugreportfull"; + break; + case ActivityManager.BUGREPORT_OPTION_INTERACTIVE: + type = "bugreportplus"; + break; + case ActivityManager.BUGREPORT_OPTION_REMOTE: + type = "bugreportremote"; + break; + case ActivityManager.BUGREPORT_OPTION_WEAR: + type = "bugreportwear"; + break; + case ActivityManager.BUGREPORT_OPTION_TELEPHONY: + type = "bugreporttelephony"; + break; + case ActivityManager.BUGREPORT_OPTION_WIFI: + type = "bugreportwifi"; + break; + default: + throw new IllegalArgumentException( + "Provided bugreport type is not correct, value: " + + bugreportType); + } + // Always log caller, even if it does not have permission to dump. + Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid()); + enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport"); + if (!TextUtils.isEmpty(shareTitle)) { if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) { String errorStr = "shareTitle should be less than " + @@ -8244,7 +8273,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean useApi = FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.USE_BUGREPORT_API); - if (useApi) { + if (useApi && bugreportType == ActivityManager.BUGREPORT_OPTION_INTERACTIVE) { // Create intent to trigger Bugreport API via Shell Intent triggerShellBugreport = new Intent(); triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED); @@ -8256,40 +8285,12 @@ public class ActivityManagerService extends IActivityManager.Stub if (shareDescription != null) { triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription); } + // Send broadcast to shell to trigger bugreport using Bugreport API + mContext.sendBroadcast(triggerShellBugreport); + } else { + SystemProperties.set("dumpstate.options", type); + SystemProperties.set("ctl.start", "bugreport"); } - String extraOptions = null; - switch (bugreportType) { - case ActivityManager.BUGREPORT_OPTION_FULL: - extraOptions = "bugreportfull"; - break; - case ActivityManager.BUGREPORT_OPTION_INTERACTIVE: - extraOptions = "bugreportplus"; - break; - case ActivityManager.BUGREPORT_OPTION_REMOTE: - extraOptions = "bugreportremote"; - break; - case ActivityManager.BUGREPORT_OPTION_WEAR: - extraOptions = "bugreportwear"; - break; - case ActivityManager.BUGREPORT_OPTION_TELEPHONY: - extraOptions = "bugreporttelephony"; - break; - case ActivityManager.BUGREPORT_OPTION_WIFI: - extraOptions = "bugreportwifi"; - break; - default: - throw new IllegalArgumentException("Provided bugreport type is not correct, value: " - + bugreportType); - } - // Always log caller, even if it does not have permission to dump. - String type = extraOptions == null ? "bugreport" : extraOptions; - Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid()); - - enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport"); - if (extraOptions != null) { - SystemProperties.set("dumpstate.options", extraOptions); - } - SystemProperties.set("ctl.start", "bugreport"); } /** @@ -12230,11 +12231,8 @@ public class ActivityManagerService extends IActivityManager.Stub proto.write(MemInfoDumpProto.MemItem.IS_PROC, mi.isProc); proto.write(MemInfoDumpProto.MemItem.ID, mi.id); proto.write(MemInfoDumpProto.MemItem.HAS_ACTIVITIES, mi.hasActivities); - if (dumpPss) { - proto.write(MemInfoDumpProto.MemItem.PSS_KB, mi.pss); - } else { - proto.write(MemInfoDumpProto.MemItem.RSS_KB, mi.mRss); - } + proto.write(MemInfoDumpProto.MemItem.PSS_KB, mi.pss); + proto.write(MemInfoDumpProto.MemItem.RSS_KB, mi.mRss); if (dumpSwapPss) { proto.write(MemInfoDumpProto.MemItem.SWAP_PSS_KB, mi.swapPss); } @@ -12532,6 +12530,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) { opts.dumpDetails = true; } + final int numProcs = procs.size(); + final boolean collectNative = !opts.isCheckinRequest && numProcs > 1 && !opts.packages; + if (collectNative) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + } dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact); @@ -12570,7 +12575,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean hasSwapPss = false; Debug.MemoryInfo mi = null; - for (int i = procs.size() - 1 ; i >= 0 ; i--) { + for (int i = numProcs - 1; i >= 0; i--) { final ProcessRecord r = procs.get(i); final IApplicationThread thread; final int pid; @@ -12724,10 +12729,7 @@ public class ActivityManagerService extends IActivityManager.Stub long nativeProcTotalPss = 0; - if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) { - // If we are showing aggregations, also look for native processes to - // include so that our aggregations are more accurate. - updateCpuStatsNow(); + if (collectNative) { mi = null; synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); @@ -13095,6 +13097,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) { opts.dumpDetails = true; } + final int numProcs = procs.size(); + final boolean collectNative = numProcs > 1 && !opts.packages; + if (collectNative) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + } ProtoOutputStream proto = new ProtoOutputStream(fd); @@ -13136,7 +13145,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean hasSwapPss = false; Debug.MemoryInfo mi = null; - for (int i = procs.size() - 1 ; i >= 0 ; i--) { + for (int i = numProcs - 1; i >= 0; i--) { final ProcessRecord r = procs.get(i); final IApplicationThread thread; final int pid; @@ -13283,10 +13292,7 @@ public class ActivityManagerService extends IActivityManager.Stub long nativeProcTotalPss = 0; - if (procs.size() > 1 && !opts.packages) { - // If we are showing aggregations, also look for native processes to - // include so that our aggregations are more accurate. - updateCpuStatsNow(); + if (collectNative) { mi = null; synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 5465309f7826..c0af814cdb38 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -65,7 +65,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.MessageQueue; import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; @@ -101,11 +100,8 @@ import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; -import libcore.io.IoUtils; - import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.nio.ByteBuffer; @@ -2087,10 +2083,10 @@ public final class ProcessList { } } } - if (lrui <= mLruProcessActivityStart) { + if (lrui < mLruProcessActivityStart) { mLruProcessActivityStart--; } - if (lrui <= mLruProcessServiceStart) { + if (lrui < mLruProcessServiceStart) { mLruProcessServiceStart--; } mLruProcesses.remove(lrui); @@ -2622,7 +2618,7 @@ public final class ProcessList { if (!moved) { // Goes to the end of the group. mLruProcesses.remove(i); - mLruProcesses.add(endIndex - 1, subProc); + mLruProcesses.add(endIndex, subProc); if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving " + subProc + " from position " + i + " to end of group @ " @@ -2867,15 +2863,6 @@ public final class ProcessList { pos--; } mLruProcesses.add(pos, app); - if (pos == mLruProcessActivityStart) { - mLruProcessActivityStart++; - } - if (pos == mLruProcessServiceStart) { - // Unless {@code #hasService} is implemented, currently the starting position - // for activity and service are the same, so the incoming position may equal to - // the starting position of service. - mLruProcessServiceStart++; - } // If this process is part of a group, need to pull up any other processes // in that group to be with it. int endIndex = pos - 1; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d4ceb5a86946..b2c40ef897aa 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2180,7 +2180,7 @@ class UserController implements Handler.Callback { BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START, Integer.toString(msg.arg1), msg.arg1); - mInjector.getSystemServiceManager().switchUser(msg.arg1); + mInjector.getSystemServiceManager().switchUser(msg.arg2, msg.arg1); break; case FOREGROUND_PROFILE_CHANGED_MSG: dispatchForegroundProfileChanged(msg.arg1); diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index 65472c9b409d..bd129f70ee29 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -405,7 +405,7 @@ public class FocusRequester { @GuardedBy("MediaFocusControl.mAudioFocusLock") private boolean frameworkHandleFocusLoss(int focusLoss, @NonNull final FocusRequester frWinner, boolean forceDuck) { - if (frWinner.mCallingUid != this.mCallingUid) { + if (frWinner.mCallingUid == this.mCallingUid) { // the focus change is within the same app, so let the dispatching // happen as if the framework was not involved. return false; diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index a7065216f6a3..ee49f5885e4a 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -223,7 +223,8 @@ public class FaceService extends BiometricServiceBase { @Override public boolean wasUserDetected() { - return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED; + return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED + && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY; } @Override diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 3e1817bd14ff..36e872a109dd 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -461,7 +461,7 @@ public final class ContentService extends IContentService.Stub { } synchronized (mCache) { - final String providerPackageName = getProviderPackageName(uri); + final String providerPackageName = getProviderPackageName(uri, userHandle); invalidateCacheLocked(userHandle, providerPackageName, uri); } } finally { @@ -1145,9 +1145,9 @@ public final class ContentService extends IContentService.Stub { } } - private @Nullable String getProviderPackageName(Uri uri) { - final ProviderInfo pi = mContext.getPackageManager() - .resolveContentProvider(uri.getAuthority(), 0); + private @Nullable String getProviderPackageName(Uri uri, int userId) { + final ProviderInfo pi = mContext.getPackageManager().resolveContentProviderAsUser( + uri.getAuthority(), 0, userId); return (pi != null) ? pi.packageName : null; } @@ -1200,7 +1200,7 @@ public final class ContentService extends IContentService.Stub { mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), packageName); - final String providerPackageName = getProviderPackageName(key); + final String providerPackageName = getProviderPackageName(key, userId); final Pair<String, Uri> fullKey = Pair.create(packageName, key); synchronized (mCache) { @@ -1222,7 +1222,7 @@ public final class ContentService extends IContentService.Stub { mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), packageName); - final String providerPackageName = getProviderPackageName(key); + final String providerPackageName = getProviderPackageName(key, userId); final Pair<String, Uri> fullKey = Pair.create(packageName, key); synchronized (mCache) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 4f33ebb035da..c466640e658b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2020,6 +2020,10 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void resizeVirtualDisplay(IVirtualDisplayCallback callback, int width, int height, int densityDpi) { + if (width <= 0 || height <= 0 || densityDpi <= 0) { + throw new IllegalArgumentException("width, height, and densityDpi must be " + + "greater than 0"); + } final long token = Binder.clearCallingIdentity(); try { resizeVirtualDisplayInternal(callback.asBinder(), width, height, densityDpi); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 9e3401882f45..e753a7be99f6 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -43,7 +43,7 @@ import java.util.Objects; * Maintains a connection to a particular media route provider service. */ final class MediaRoute2ProviderProxy implements ServiceConnection { - private static final String TAG = "MediaRoute2ProviderProxy"; + private static final String TAG = "MediaRoute2Provider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; @@ -54,7 +54,6 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { private Callback mCallback; - //TODO: make it nonnull private MediaRoute2ProviderInfo mProviderInfo; // Connection state @@ -246,13 +245,21 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { if (mActiveConnection != connection) { return; } - // Set a unique provider id for identifying providers. - mProviderInfo = new MediaRoute2ProviderInfo.Builder(info) - .setUniqueId(mUniqueId) - .build(); if (DEBUG) { Slog.d(TAG, this + ": State changed "); } + setAndNotifyProviderInfo(info); + } + + private void setAndNotifyProviderInfo(MediaRoute2ProviderInfo info) { + //TODO: check if info is not updated + if (info == null) { + mProviderInfo = null; + } else { + mProviderInfo = new MediaRoute2ProviderInfo.Builder(info) + .setUniqueId(mUniqueId) + .build(); + } mHandler.post(mStateChanged); } @@ -261,6 +268,7 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; + setAndNotifyProviderInfo(null); } } @@ -337,7 +345,7 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { mHandler.post(() -> onConnectionDied(Connection.this)); } - void postProviderUpdated(MediaRoute2ProviderInfo info) { + void postProviderInfoUpdated(MediaRoute2ProviderInfo info) { mHandler.post(() -> onProviderInfoUpdated(Connection.this, info)); } } @@ -354,10 +362,10 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } @Override - public void notifyProviderInfoUpdated(MediaRoute2ProviderInfo info) { + public void updateProviderInfo(MediaRoute2ProviderInfo info) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postProviderUpdated(info); + connection.postProviderInfoUpdated(info); } } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 12137fe8d841..043c834230d1 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -33,6 +33,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -72,21 +73,20 @@ class MediaRouter2ServiceImpl { mContext = context; } - public void registerClientAsUser(@NonNull IMediaRouter2Client client, - @NonNull String packageName, int userId) { + public void registerClient(@NonNull IMediaRouter2Client client, + @NonNull String packageName) { Objects.requireNonNull(client, "client must not be null"); final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); - final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, - false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName); + final int userId = UserHandle.getUserId(uid); final boolean trusted = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == PackageManager.PERMISSION_GRANTED; final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted); + registerClientLocked(client, uid, pid, packageName, userId, trusted); } } finally { Binder.restoreCallingIdentity(token); @@ -106,20 +106,20 @@ class MediaRouter2ServiceImpl { } } - public void registerManagerAsUser(@NonNull IMediaRouter2Manager manager, - @NonNull String packageName, int userId) { + public void registerManager(@NonNull IMediaRouter2Manager manager, + @NonNull String packageName) { Objects.requireNonNull(manager, "manager must not be null"); //TODO: should check permission final boolean trusted = true; final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); - final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, - false /*allowAll*/, true /*requireFull*/, "registerManagerAsUser", packageName); + final int userId = UserHandle.getUserId(uid); + final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerManagerLocked(manager, uid, pid, packageName, resolvedUserId, trusted); + registerManagerLocked(manager, uid, pid, packageName, userId, trusted); } } finally { Binder.restoreCallingIdentity(token); @@ -254,6 +254,10 @@ class MediaRouter2ServiceImpl { userRecord.mClientRecords.add(clientRecord); mAllClientRecords.put(binder, clientRecord); + + userRecord.mHandler.sendMessage( + obtainMessage(UserHandler::notifyProviderInfosUpdatedToClient, + userRecord.mHandler, client)); } } @@ -341,9 +345,9 @@ class MediaRouter2ServiceImpl { userRecord.mManagerRecords.add(managerRecord); mAllManagerRecords.put(binder, managerRecord); - //TODO: remove this when it's unnecessary - // Sends published routes to newly added manager. - userRecord.mHandler.scheduleUpdateManagerState(); + userRecord.mHandler.sendMessage( + obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager, + userRecord.mHandler, manager)); final int count = userRecord.mClientRecords.size(); for (int i = 0; i < count; i++) { @@ -504,14 +508,14 @@ class MediaRouter2ServiceImpl { private final WeakReference<MediaRouter2ServiceImpl> mServiceRef; private final UserRecord mUserRecord; private final MediaRoute2ProviderWatcher mWatcher; - private final ArrayList<IMediaRouter2Manager> mTempManagers = new ArrayList<>(); //TODO: Make this thread-safe. private final ArrayList<MediaRoute2ProviderProxy> mMediaProviders = new ArrayList<>(); + private List<MediaRoute2ProviderInfo> mProviderInfos; private boolean mRunning; - private boolean mManagerStateUpdateScheduled; + private boolean mProviderInfosUpdateScheduled; UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) { super(Looper.getMainLooper(), null, true); @@ -553,14 +557,14 @@ class MediaRouter2ServiceImpl { } private void updateProvider(MediaRoute2ProviderProxy provider) { - scheduleUpdateManagerState(); + scheduleUpdateProviderInfos(); } private void selectRoute(ClientRecord clientRecord, MediaRoute2Info route) { if (route != null) { MediaRoute2ProviderProxy provider = findProvider(route.getProviderId()); if (provider == null) { - Log.w(TAG, "Ignoring to select route of unknown provider " + route); + Slog.w(TAG, "Ignoring to select route of unknown provider " + route); } else { provider.selectRoute(clientRecord.mPackageName, route.getId()); } @@ -571,7 +575,7 @@ class MediaRouter2ServiceImpl { if (route != null) { MediaRoute2ProviderProxy provider = findProvider(route.getProviderId()); if (provider == null) { - Log.w(TAG, "Ignoring to unselect route of unknown provider " + route); + Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route); } else { provider.unselectRoute(clientRecord.mPackageName, route.getId()); } @@ -585,49 +589,71 @@ class MediaRouter2ServiceImpl { } } - private void scheduleUpdateManagerState() { - if (!mManagerStateUpdateScheduled) { - mManagerStateUpdateScheduled = true; - sendMessage(PooledLambda.obtainMessage(UserHandler::updateManagerState, this)); + private void scheduleUpdateProviderInfos() { + if (!mProviderInfosUpdateScheduled) { + mProviderInfosUpdateScheduled = true; + sendMessage(PooledLambda.obtainMessage(UserHandler::updateProviderInfos, this)); } } - private void updateManagerState() { - mManagerStateUpdateScheduled = false; + private void updateProviderInfos() { + mProviderInfosUpdateScheduled = false; MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { return; } - //TODO: Consider using a member variable (like mTempManagers). + final List<IMediaRouter2Manager> managers = new ArrayList<>(); + final List<IMediaRouter2Client> clients = new ArrayList<>(); final List<MediaRoute2ProviderInfo> providers = new ArrayList<>(); for (MediaRoute2ProviderProxy mediaProvider : mMediaProviders) { final MediaRoute2ProviderInfo providerInfo = mediaProvider.getProviderInfo(); if (providerInfo == null || !providerInfo.isValid()) { - Log.w(TAG, "Ignoring invalid provider info : " + providerInfo); + Slog.w(TAG, "Ignoring invalid provider info : " + providerInfo); } else { providers.add(providerInfo); } } + mProviderInfos = providers; - try { - synchronized (service.mLock) { - final int count = mUserRecord.mManagerRecords.size(); - for (int i = 0; i < count; i++) { - mTempManagers.add(mUserRecord.mManagerRecords.get(i).mManager); - } + synchronized (service.mLock) { + for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { + managers.add(managerRecord.mManager); } - for (IMediaRouter2Manager tempManager : mTempManagers) { - try { - tempManager.notifyProviderInfosUpdated(providers); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to update manager state. Manager probably died.", ex); - } + for (ClientRecord clientRecord : mUserRecord.mClientRecords) { + clients.add(clientRecord.mClient); } - } finally { - // Clear the list in preparation for the next time. - mTempManagers.clear(); + } + for (IMediaRouter2Manager manager : managers) { + notifyProviderInfosUpdatedToManager(manager); + } + for (IMediaRouter2Client client : clients) { + notifyProviderInfosUpdatedToClient(client); + } + } + + private void notifyProviderInfosUpdatedToClient(IMediaRouter2Client client) { + if (mProviderInfos == null) { + scheduleUpdateProviderInfos(); + return; + } + try { + client.notifyProviderInfosUpdated(mProviderInfos); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify provider infos updated. Client probably died."); + } + } + + private void notifyProviderInfosUpdatedToManager(IMediaRouter2Manager manager) { + if (mProviderInfos == null) { + scheduleUpdateProviderInfos(); + return; + } + try { + manager.notifyProviderInfosUpdated(mProviderInfos); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify provider infos updated. Manager probably died."); } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index a43068b87c06..2670cd8247ac 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -435,12 +435,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId) { + public void registerClient2(IMediaRouter2Client client, String packageName) { final int uid = Binder.getCallingUid(); if (!validatePackageName(uid, packageName)) { throw new SecurityException("packageName must match the calling uid"); } - mService2.registerClientAsUser(client, packageName, userId); + mService2.registerClient(client, packageName); } // Binder call @@ -464,13 +464,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void registerManagerAsUser(IMediaRouter2Manager manager, - String packageName, int userId) { + public void registerManager(IMediaRouter2Manager manager, String packageName) { final int uid = Binder.getCallingUid(); if (!validatePackageName(uid, packageName)) { throw new SecurityException("packageName must match the calling uid"); } - mService2.registerManagerAsUser(manager, packageName, userId); + mService2.registerManager(manager, packageName); } // Binder call diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e90db3855b65..30b2245ec24e 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -315,7 +315,7 @@ public class NotificationManagerService extends SystemService { static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; + static final int INVALID_UID = -1; static final boolean ENABLE_BLOCKED_TOASTS = true; @@ -911,8 +911,22 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { + final boolean fgService; + synchronized (mNotificationLock) { + NotificationRecord r = findNotificationLocked(pkg, tag, id, userId); + fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0; + } cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId, REASON_ERROR, null); + if (fgService) { + // Still crash for foreground services, preventing the not-crash behaviour abused + // by apps to give us a garbage notification and silently start a fg service. + Binder.withCleanCallingIdentity( + () -> mAm.crashApplication(uid, initialPid, pkg, -1, + "Bad notification(tag=" + tag + ", id=" + id + ") posted from package " + + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): " + + message)); + } } @Override @@ -4699,6 +4713,12 @@ public class NotificationManagerService extends SystemService { // ensure opPkg is delegate if does not match pkg int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + if (uid == INVALID_UID) { + Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification " + + "for nonexistent pkg " + pkg + " in user " + userId); + return; + } + // if opPkg is not the same as pkg, make sure the notification given was posted // by opPkg if (!Objects.equals(pkg, opPkg)) { @@ -4743,6 +4763,11 @@ public class NotificationManagerService extends SystemService { // as "pkg" final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + if (notificationUid == INVALID_UID) { + throw new SecurityException("Caller " + opPkg + ":" + callingUid + + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); + } + checkRestrictedCategories(notification); // Fix the notification as best we can. @@ -5063,8 +5088,7 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - int resolveNotificationUid(String callingPkg, String targetPkg, - int callingUid, int userId) { + int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) { if (userId == UserHandle.USER_ALL) { userId = USER_SYSTEM; } @@ -5075,16 +5099,16 @@ public class NotificationManagerService extends SystemService { return callingUid; } - int targetUid = -1; + int targetUid = INVALID_UID; try { targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); } catch (NameNotFoundException e) { - /* ignore */ + /* ignore, handled by caller */ } // posted from app A on behalf of app B - if (targetUid != -1 && (isCallerAndroid(callingPkg, callingUid) + if (isCallerAndroid(callingPkg, callingUid) || mPreferencesHelper.isDelegateAllowed( - targetPkg, targetUid, callingPkg, callingUid))) { + targetPkg, targetUid, callingPkg, callingUid)) { return targetUid; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 92ac1d48d784..d580bd6f0a11 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -74,6 +74,9 @@ public class PreferencesHelper implements RankingConfig { private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @VisibleForTesting + static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; + + @VisibleForTesting static final String TAG_RANKING = "ranking"; private static final String TAG_PACKAGE = "package"; private static final String TAG_CHANNEL = "channel"; @@ -180,6 +183,7 @@ public class PreferencesHelper implements RankingConfig { // noop } } + boolean skipWarningLogged = false; PackagePreferences r = getOrCreatePackagePreferencesLocked(name, uid, XmlUtils.readIntAttribute( @@ -226,6 +230,14 @@ public class PreferencesHelper implements RankingConfig { } // Channels if (TAG_CHANNEL.equals(tagName)) { + if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) { + if (!skipWarningLogged) { + Slog.w(TAG, "Skipping further channels for " + r.pkg + + "; app has too many"); + skipWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); String channelName = parser.getAttributeValue(null, ATT_NAME); int channelImportance = XmlUtils.readIntAttribute( @@ -696,6 +708,10 @@ public class PreferencesHelper implements RankingConfig { return needsPolicyFileChange; } + if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more channels"); + } + needsPolicyFileChange = true; if (channel.getImportance() < IMPORTANCE_NONE diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a1b6d492a4a3..1da5bc6c26ee 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -38,6 +38,7 @@ import android.sysprop.ApexProperties; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import java.io.File; @@ -263,7 +264,8 @@ abstract class ApexManager { populateAllPackagesCacheIfNeeded(); mContext.unregisterReceiver(this); } - }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), /* broadcastPermission */ null, + BackgroundThread.getHandler()); } private void populateAllPackagesCacheIfNeeded() { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index e2644ffb9301..fda798caf433 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -43,7 +43,6 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import static com.android.server.pm.permission.PermissionManagerService.killUid; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE; import static java.util.concurrent.TimeUnit.SECONDS; @@ -782,6 +781,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public int checkPermission(String permName, String pkgName, int userId) { + // Not using Preconditions.checkNotNull() here for compatibility reasons. + if (permName == null || pkgName == null) { + return PackageManager.PERMISSION_DENIED; + } + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { if (mCheckPermissionDelegate == null) { @@ -794,43 +801,70 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private int checkPermissionImpl(String permName, String pkgName, int userId) { - return checkPermissionInternal(permName, pkgName, getCallingUid(), userId); - } - - private int checkPermissionInternal(String permName, String packageName, int callingUid, - int userId) { - if (!mUserManagerInt.exists(userId)) { + final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName); + if (pkg == null) { return PackageManager.PERMISSION_DENIED; } - final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName); - if (pkg != null && pkg.mExtras != null) { + return checkPermissionInternal(pkg, true, permName, userId); + } + + private int checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit, + @NonNull String permissionName, @UserIdInt int userId) { + final int callingUid = getCallingUid(); + if (isPackageExplicit || pkg.mSharedUserId == null) { if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { return PackageManager.PERMISSION_DENIED; } - final PackageSetting ps = (PackageSetting) pkg.mExtras; - final boolean instantApp = ps.getInstantApp(userId); - final PermissionsState permissionsState = ps.getPermissionsState(); - if (permissionsState.hasPermission(permName, userId)) { - if (instantApp) { - synchronized (mLock) { - BasePermission bp = mSettings.getPermissionLocked(permName); - if (bp != null && bp.isInstant()) { - return PackageManager.PERMISSION_GRANTED; - } - } - } else { - return PackageManager.PERMISSION_GRANTED; - } - } - if (isImpliedPermissionGranted(permissionsState, permName, userId)) { - return PackageManager.PERMISSION_GRANTED; + } else { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return PackageManager.PERMISSION_DENIED; } } + + final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid); + final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (ps == null) { + return PackageManager.PERMISSION_DENIED; + } + final PermissionsState permissionsState = ps.getPermissionsState(); + + if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) { + return PackageManager.PERMISSION_GRANTED; + } + + final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); + if (fullerPermissionName != null + && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) { + return PackageManager.PERMISSION_GRANTED; + } + return PackageManager.PERMISSION_DENIED; } + private boolean checkSinglePermissionInternal(int uid, + @NonNull PermissionsState permissionsState, @NonNull String permissionName) { + if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) { + return false; + } + + if (mPackageManagerInt.getInstantAppPackageName(uid) != null) { + return mSettings.isPermissionInstant(permissionName); + } + + return true; + } + @Override public int checkUidPermission(String permName, int uid) { + // Not using Preconditions.checkNotNull() here for compatibility reasons. + if (permName == null) { + return PackageManager.PERMISSION_DENIED; + } + final int userId = UserHandle.getUserId(uid); + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { if (mCheckPermissionDelegate == null) { @@ -842,11 +876,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this::checkUidPermissionImpl); } - private int checkUidPermissionImpl(@NonNull String permName, int uid) { + private int checkUidPermissionImpl(String permName, int uid) { final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid); - synchronized (mLock) { - return checkUidPermissionInternal(permName, pkg, uid, getCallingUid()); - } + return checkUidPermissionInternal(pkg, uid, permName); } /** @@ -856,55 +888,33 @@ public class PermissionManagerService extends IPermissionManager.Stub { * * @see SystemConfig#getSystemPermissions() */ - private int checkUidPermissionInternal(@NonNull String permName, - @Nullable PackageParser.Package pkg, int uid, int callingUid) { - final int callingUserId = UserHandle.getUserId(callingUid); - final boolean isCallerInstantApp = - mPackageManagerInt.getInstantAppPackageName(callingUid) != null; - final boolean isUidInstantApp = - mPackageManagerInt.getInstantAppPackageName(uid) != null; - final int userId = UserHandle.getUserId(uid); - if (!mUserManagerInt.exists(userId)) { - return PackageManager.PERMISSION_DENIED; + private int checkUidPermissionInternal(@Nullable Package pkg, int uid, + @NonNull String permissionName) { + if (pkg != null) { + final int userId = UserHandle.getUserId(uid); + return checkPermissionInternal(pkg, false, permissionName, userId); } - if (pkg != null) { - if (pkg.mSharedUserId != null) { - if (isCallerInstantApp) { - return PackageManager.PERMISSION_DENIED; - } - } else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) { - return PackageManager.PERMISSION_DENIED; - } - final PermissionsState permissionsState = - ((PackageSetting) pkg.mExtras).getPermissionsState(); - if (permissionsState.hasPermission(permName, userId)) { - if (isUidInstantApp) { - if (mSettings.isPermissionInstant(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - return PackageManager.PERMISSION_GRANTED; - } - } - if (isImpliedPermissionGranted(permissionsState, permName, userId)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - ArraySet<String> perms = mSystemPermissions.get(uid); - if (perms != null) { - if (perms.contains(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - if (FULLER_PERMISSION_MAP.containsKey(permName) - && perms.contains(FULLER_PERMISSION_MAP.get(permName))) { - return PackageManager.PERMISSION_GRANTED; - } - } + if (checkSingleUidPermissionInternal(uid, permissionName)) { + return PackageManager.PERMISSION_GRANTED; + } + + final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); + if (fullerPermissionName != null + && checkSingleUidPermissionInternal(uid, fullerPermissionName)) { + return PackageManager.PERMISSION_GRANTED; } + return PackageManager.PERMISSION_DENIED; } + private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) { + synchronized (mLock) { + ArraySet<String> permissions = mSystemPermissions.get(uid); + return permissions != null && permissions.contains(permissionName); + } + } + @Override public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { mContext.enforceCallingOrSelfPermission( @@ -1962,18 +1972,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - /** - * Returns {@code true} if the permission can be implied from another granted permission. - * <p>Some permissions, such as ACCESS_FINE_LOCATION, imply other permissions, - * such as ACCESS_COURSE_LOCATION. If the caller holds an umbrella permission, give - * it access to any implied permissions. - */ - private static boolean isImpliedPermissionGranted(PermissionsState permissionsState, - String permName, int userId) { - return FULLER_PERMISSION_MAP.containsKey(permName) - && permissionsState.hasPermission(FULLER_PERMISSION_MAP.get(permName), userId); - } - private int adjustPermissionProtectionFlagsLocked( int protectionLevel, String packageName, int uid) { // Signature permission flags area always reported @@ -2062,8 +2060,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int numPackages = allPackageNames.size(); for (int packageNum = 0; packageNum < numPackages; packageNum++) { final String packageName = allPackageNames.get(packageNum); - final int permissionState = checkPermissionInternal( - permissionName, packageName, UserHandle.USER_SYSTEM, userId); + final int permissionState = checkPermission(permissionName, packageName, + userId); if (permissionState == PackageManager.PERMISSION_GRANTED) { EventLog.writeEvent(0x534e4554, "72710897", newPackage.applicationInfo.uid, diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java index 77bf930fb4d7..712012d9e621 100644 --- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java +++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java @@ -24,20 +24,17 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; -import android.os.Debug; import android.provider.Settings; -import android.telecom.TelecomManager; import android.text.TextUtils; -import android.util.Log; import android.util.Slog; +import com.android.internal.R; import com.android.internal.telephony.SmsApplication; import com.android.internal.util.CollectionUtils; import com.android.server.LocalServices; import com.android.server.role.RoleManagerService; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -67,14 +64,25 @@ public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHolder public List<String> getRoleHolders(@NonNull String roleName, @UserIdInt int userId) { switch (roleName) { case RoleManager.ROLE_ASSISTANT: { - String legacyAssistant = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId); - if (legacyAssistant == null || legacyAssistant.isEmpty()) { - return Collections.emptyList(); + String packageName; + String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.ASSISTANT, userId); + // AssistUtils was using the default assistant app if Settings.Secure.ASSISTANT is + // null, while only an empty string means user selected "None". + if (setting != null) { + if (!setting.isEmpty()) { + ComponentName componentName = ComponentName.unflattenFromString(setting); + packageName = componentName != null ? componentName.getPackageName() : null; + } else { + packageName = null; + } + } else if (mContext.getPackageManager().isDeviceUpgrading()) { + String defaultAssistant = mContext.getString(R.string.config_defaultAssistant); + packageName = !TextUtils.isEmpty(defaultAssistant) ? defaultAssistant : null; } else { - return Collections.singletonList( - ComponentName.unflattenFromString(legacyAssistant).getPackageName()); + packageName = null; } + return CollectionUtils.singletonOrEmpty(packageName); } case RoleManager.ROLE_BROWSER: { PackageManagerInternal packageManagerInternal = LocalServices.getService( @@ -84,44 +92,36 @@ public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHolder return CollectionUtils.singletonOrEmpty(packageName); } case RoleManager.ROLE_DIALER: { - String setting = Settings.Secure.getStringForUser( - mContext.getContentResolver(), + String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.DIALER_DEFAULT_APPLICATION, userId); - return CollectionUtils.singletonOrEmpty(!TextUtils.isEmpty(setting) - ? setting - : mContext.getSystemService(TelecomManager.class).getSystemDialerPackage()); + String packageName; + if (!TextUtils.isEmpty(setting)) { + packageName = setting; + } else if (mContext.getPackageManager().isDeviceUpgrading()) { + // DefaultDialerManager was using the default dialer app if + // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid. + // TelecomManager.getSystemDialerPackage() won't work because it might not + // be ready. + packageName = mContext.getString(R.string.config_defaultDialer); + } else { + packageName = null; + } + return CollectionUtils.singletonOrEmpty(packageName); } case RoleManager.ROLE_SMS: { - // Moved over from SmsApplication#getApplication - String result = Settings.Secure.getStringForUser( - mContext.getContentResolver(), + String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.SMS_DEFAULT_APPLICATION, userId); - // TODO: STOPSHIP: Remove the following code once we read the value of - // config_defaultSms in RoleControllerService. - if (result == null) { - Collection<SmsApplication.SmsApplicationData> applications = - SmsApplication.getApplicationCollectionAsUser(mContext, userId); - SmsApplication.SmsApplicationData applicationData; - String defaultPackage = mContext.getResources() - .getString(com.android.internal.R.string.default_sms_application); - applicationData = - SmsApplication.getApplicationForPackage(applications, defaultPackage); - - if (applicationData == null) { - // Are there any applications? - if (applications.size() != 0) { - applicationData = - (SmsApplication.SmsApplicationData) applications.toArray()[0]; - } - } - if (DEBUG) { - Log.i(LOG_TAG, "Found default sms app: " + applicationData - + " among: " + applications + " from " + Debug.getCallers(4)); - } - SmsApplication.SmsApplicationData app = applicationData; - result = app == null ? null : app.mPackageName; + String packageName; + if (!TextUtils.isEmpty(setting)) { + packageName = setting; + } else if (mContext.getPackageManager().isDeviceUpgrading()) { + // SmsApplication was using the default SMS app if + // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid. + packageName = mContext.getString(R.string.config_defaultSms); + } else { + packageName = null; } - return CollectionUtils.singletonOrEmpty(result); + return CollectionUtils.singletonOrEmpty(packageName); } case RoleManager.ROLE_HOME: { PackageManager packageManager = mContext.getPackageManager(); diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 89a530514263..4c935643da55 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -142,11 +142,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection."); callback.onFailure(); } else if (userState.isBoundLocked()) { userState.mService.onSuggestSelection(sessionId, request, callback); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("suggestSelection", () -> onSuggestSelection(sessionId, request, callback), callback::onFailure, callback.asBinder(), this, userState)); } @@ -166,11 +167,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText."); callback.onFailure(); } else if (userState.isBoundLocked()) { userState.mService.onClassifyText(sessionId, request, callback); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("classifyText", () -> onClassifyText(sessionId, request, callback), callback::onFailure, callback.asBinder(), this, userState)); } @@ -190,11 +192,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks."); callback.onFailure(); } else if (userState.isBoundLocked()) { userState.mService.onGenerateLinks(sessionId, request, callback); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("generateLinks", () -> onGenerateLinks(sessionId, request, callback), callback::onFailure, callback.asBinder(), this, userState)); } @@ -214,7 +217,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (userState.isBoundLocked()) { userState.mService.onSelectionEvent(sessionId, event); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("selectionEvent", () -> onSelectionEvent(sessionId, event), null /* onServiceFailure */, null /* binder */, this, userState)); } @@ -238,7 +241,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (userState.isBoundLocked()) { userState.mService.onTextClassifierEvent(sessionId, event); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("textClassifierEvent", () -> onTextClassifierEvent(sessionId, event), null /* onServiceFailure */, null /* binder */, this, userState)); } @@ -258,11 +261,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage."); callback.onFailure(); } else if (userState.isBoundLocked()) { userState.mService.onDetectLanguage(sessionId, request, callback); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("detectLanguage", () -> onDetectLanguage(sessionId, request, callback), callback::onFailure, callback.asBinder(), this, userState)); } @@ -282,11 +286,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { + Slog.d(LOG_TAG, + "Unable to bind TextClassifierService at suggestConversationActions."); callback.onFailure(); } else if (userState.isBoundLocked()) { userState.mService.onSuggestConversationActions(sessionId, request, callback); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("suggestConversationActions", () -> onSuggestConversationActions(sessionId, request, callback), callback::onFailure, callback.asBinder(), this, userState)); } @@ -309,7 +315,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi classificationContext, sessionId); mSessionUserIds.put(sessionId, userId); } else { - userState.mPendingRequests.add(new PendingRequest( + userState.mPendingRequests.add(new PendingRequest("createTextClassificationSession", () -> onCreateTextClassificationSession(classificationContext, sessionId), null /* onServiceFailure */, null /* binder */, this, userState)); } @@ -332,9 +338,10 @@ public final class TextClassificationManagerService extends ITextClassifierServi userState.mService.onDestroyTextClassificationSession(sessionId); mSessionUserIds.remove(sessionId); } else { - userState.mPendingRequests.add(new PendingRequest( - () -> onDestroyTextClassificationSession(sessionId), - null /* onServiceFailure */, null /* binder */, this, userState)); + userState.mPendingRequests.add( + new PendingRequest("destroyTextClassificationSession", + () -> onDestroyTextClassificationSession(sessionId), + null /* onServiceFailure */, null /* binder */, this, userState)); } } } @@ -379,6 +386,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private static final class PendingRequest implements IBinder.DeathRecipient { + @Nullable private final String mName; @Nullable private final IBinder mBinder; @NonNull private final Runnable mRequest; @Nullable private final Runnable mOnServiceFailure; @@ -394,11 +402,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi * @param service * @param owningUser */ - PendingRequest( + PendingRequest(@Nullable String name, @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure, @Nullable IBinder binder, TextClassificationManagerService service, UserState owningUser) { + mName = name; mRequest = logOnFailure(Preconditions.checkNotNull(request), "handling pending request"); mOnServiceFailure = @@ -499,6 +508,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.mRequest.run(); } else { if (request.mOnServiceFailure != null) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService for PendingRequest " + + request.mName); request.mOnServiceFailure.run(); } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index a2134be9c8ef..8e66b149d344 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -842,6 +842,10 @@ public final class TvInputManagerService extends SystemService { private void setStateLocked(String inputId, int state, int userId) { UserState userState = getOrCreateUserStateLocked(userId); TvInputState inputState = userState.inputMap.get(inputId); + if (inputState == null) { + Slog.e(TAG, "failed to setStateLocked - unknown input id " + inputId); + return; + } ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent()); int oldState = inputState.state; inputState.state = state; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 26ca975bfc8f..30a3aef01fdb 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -124,11 +124,11 @@ final class AccessibilityController { return false; } - final Display display = dc.getDisplay(); if (mWindowsForAccessibilityObserver.get(displayId) != null) { + final Display display = dc.getDisplay(); if (display.getType() == Display.TYPE_VIRTUAL && dc.getParentWindow() != null) { // The window observer of this embedded display had been set from - // window manager after setting its parent window + // window manager after setting its parent window. return true; } else { throw new IllegalStateException( @@ -136,9 +136,6 @@ final class AccessibilityController { + displayId + " already set!"); } } - if (display.getType() == Display.TYPE_OVERLAY) { - return false; - } mWindowsForAccessibilityObserver.put(displayId, new WindowsForAccessibilityObserver(mService, displayId, callback)); } else { @@ -287,9 +284,8 @@ final class AccessibilityController { } public boolean hasCallbacksLocked() { - // TODO: support multi-display for windows observer return (mDisplayMagnifiers.size() > 0 - || mWindowsForAccessibilityObserver != null); + || mWindowsForAccessibilityObserver.size() > 0); } public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) { @@ -1158,15 +1154,15 @@ final class AccessibilityController { } List<WindowInfo> windows = new ArrayList<>(); + final int topFocusedDisplayId; + IBinder topFocusedWindowToken = null; synchronized (mService.mGlobalLock) { - // Do not send the windows if there is no current focus as + // Do not send the windows if there is no top focus as // the window manager is still looking for where to put it. // We will do the work when we get a focus change callback. - // TODO [Multi-Display] : only checks top focused window - if (!isCurrentFocusWindowOnDefaultDisplay()) { - return; - } + final WindowState topFocusedWindowState = getTopFocusWindow(); + if (topFocusedWindowState == null) return; final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId); if (dc == null) { @@ -1229,9 +1225,13 @@ final class AccessibilityController { visibleWindows.clear(); addedWindows.clear(); - } - mCallback.onWindowsForAccessibilityChanged(forceSend, windows); + // Gets the top focused display Id and window token for supporting multi-display. + topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId(); + topFocusedWindowToken = topFocusedWindowState.mClient.asBinder(); + } + mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId, + topFocusedWindowToken, windows); // Recycle the windows as we do not need them. clearAndRecycleWindows(windows); @@ -1410,22 +1410,9 @@ final class AccessibilityController { } return displayParentWindow; } - // TODO [Multi-Display] : only checks top focused window - private boolean isCurrentFocusWindowOnDefaultDisplay() { - final WindowState focusedWindow = - mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus; - if (focusedWindow == null) { - return false; - } - - final WindowState rootDisplayParentWindow = findRootDisplayParentWindow(focusedWindow); - if (!focusedWindow.isDefaultDisplay() - && (rootDisplayParentWindow == null - || !rootDisplayParentWindow.isDefaultDisplay())) { - return false; - } - return true; + private WindowState getTopFocusWindow() { + return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus; } private class MyHandler extends Handler { diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 5a0dfd02b7b0..e76078fb31b1 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -362,7 +362,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return null; } - private boolean alwaysCreateStack(int windowingMode, int activityType) { + boolean alwaysCreateStack(int windowingMode, int activityType) { // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing // modes so that we can manage visual ordering and return types correctly. return activityType == ACTIVITY_TYPE_STANDARD @@ -490,10 +490,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return null; } - ActivityStack getNextFocusableStack() { - return getNextFocusableStack(null /* currentFocus */, false /* ignoreCurrent */); - } - ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) { final int currentWindowingMode = currentFocus != null ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2269537c7cae..fc36e9984c1b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -106,6 +106,7 @@ import static com.android.server.am.ActivityRecordProto.VISIBLE; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; @@ -115,14 +116,13 @@ import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STARTED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; -import static com.android.server.wm.ActivityStack.LAUNCH_TICK; -import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG; -import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG; +import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; -import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG; import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; +import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS; @@ -134,6 +134,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; @@ -178,8 +180,10 @@ import android.app.WaitResult.LaunchState; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; +import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.MoveToDisplayItem; import android.app.servertransaction.MultiWindowModeChangeItem; import android.app.servertransaction.NewIntentItem; @@ -199,12 +203,12 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; import android.graphics.Rect; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; -import android.os.Message; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -261,6 +265,8 @@ import java.util.Objects; */ final class ActivityRecord extends ConfigurationContainer { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM; + private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; + private static final String TAG_APP = TAG + POSTFIX_APP; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; @@ -283,6 +289,9 @@ final class ActivityRecord extends ConfigurationContainer { private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; + // How many activities have to be scheduled to stop to force a stop pass. + private static final int MAX_STOPPING_TO_FORCE = 3; + final ActivityTaskManagerService mAtmService; // owner final IApplicationToken.Stub appToken; // window manager token // TODO: Remove after unification @@ -1381,6 +1390,12 @@ final class ActivityRecord extends ConfigurationContainer { } // Keep track of the number of fullscreen activities in this task. task.numFullscreen += occludesParent ? +1 : -1; + fullscreen = occludesParent; + } + // Always ensure visibility if this activity doesn't occlude parent, so the + // {@link #returningOptions} of the activity under this one can be applied in + // {@link #handleAlreadyVisible()}. + if (changed || !occludesParent) { mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); } return changed; @@ -1592,8 +1607,7 @@ final class ActivityRecord extends ConfigurationContainer { if (!Objects.equals(cur.taskAffinity, taskAffinity)) { break; } - cur.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "request-affinity", true /* oomAdj */); + cur.finishIfPossible("request-affinity", true /* oomAdj */); } } @@ -1650,14 +1664,17 @@ final class ActivityRecord extends ConfigurationContainer { @interface FinishRequest {} /** - * See {@link #finishActivityLocked(int, Intent, String, boolean, boolean)} + * See {@link #finishIfPossible(int, Intent, String, boolean, boolean)} */ - @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason, - boolean oomAdj) { - return finishActivityLocked(resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY); + @FinishRequest int finishIfPossible(String reason, boolean oomAdj) { + return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason, + oomAdj, !PAUSE_IMMEDIATELY); } /** + * Finish activity if possible. If activity was resumed - we must first pause it to make the + * activity below resumed. Otherwise we will try to complete the request immediately by calling + * {@link #completeFinishing(String)}. * @return One of {@link FinishRequest} values: * {@link #FINISH_RESULT_REMOVED} if this activity has been removed from the history list. * {@link #FINISH_RESULT_REQUESTED} if removal process was started, but it is still in the list @@ -1665,7 +1682,7 @@ final class ActivityRecord extends ConfigurationContainer { * {@link #FINISH_RESULT_CANCELLED} if activity is already finishing or in invalid state and the * request to finish it was not ignored. */ - @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason, + @FinishRequest int finishIfPossible(int resultCode, Intent resultData, String reason, boolean oomAdj, boolean pauseImmediately) { if (DEBUG_RESULTS || DEBUG_STATES) { Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode @@ -1704,20 +1721,24 @@ final class ActivityRecord extends ConfigurationContainer { pauseKeyDispatchingLocked(); final ActivityStack stack = getActivityStack(); - stack.adjustFocusedActivityStack(this, "finishActivity"); + stack.adjustFocusedActivityStack(this, "finishIfPossible"); finishActivityResults(resultCode, resultData); final boolean endTask = index <= 0 && !task.isClearingToReuseTask(); final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE; - if (stack.getResumedActivity() == this) { - if (DEBUG_VISIBILITY || DEBUG_TRANSITION) { - Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this); - } + if (isState(RESUMED)) { if (endTask) { mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted( task.getTaskInfo()); } + // Prepare app close transition, but don't execute just yet. It is possible that + // an activity that will be made resumed in place of this one will immediately + // launch another new activity. In this case current closing transition will be + // combined with open transition for the new activity. + if (DEBUG_VISIBILITY || DEBUG_TRANSITION) { + Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this); + } getDisplay().mDisplayContent.prepareAppTransition(transit, false); // When finishing the activity preemptively take the snapshot before the app window @@ -1744,17 +1765,17 @@ final class ActivityRecord extends ConfigurationContainer { mAtmService.getLockTaskController().clearLockedTask(task); } } else if (!isState(PAUSING)) { - // If the activity is PAUSING, we will complete the finish once - // it is done pausing; else we can just directly finish it here. - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + this); if (visible) { + // Prepare and execute close transition. prepareActivityHideTransitionAnimation(transit); } - final int finishMode = (visible || nowVisible) ? FINISH_AFTER_VISIBLE - : FINISH_AFTER_PAUSE; - final boolean removedActivity = finishCurrentActivityLocked(finishMode, oomAdj, - "finishActivityLocked") == null; + final boolean removedActivity = completeFinishing("finishIfPossible") == null; + // Performance optimization - only invoke OOM adjustment if the state changed to + // 'STOPPING'. Otherwise it will not change the OOM scores. + if (oomAdj && isState(STOPPING)) { + mAtmService.updateOomAdj(); + } // The following code is an optimization. When the last non-task overlay activity // is removed from the task, we remove the entire task from the stack. However, @@ -1771,6 +1792,7 @@ final class ActivityRecord extends ConfigurationContainer { taskOverlay.prepareActivityHideTransitionAnimation(transit); } } + return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED; } else { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + this); @@ -1789,101 +1811,298 @@ final class ActivityRecord extends ConfigurationContainer { dc.executeAppTransition(); } - static final int FINISH_IMMEDIATELY = 0; - private static final int FINISH_AFTER_PAUSE = 1; - static final int FINISH_AFTER_VISIBLE = 2; + /** + * Complete activity finish request that was initiated earlier. If the activity is still + * pausing we will wait for it to complete its transition. If the activity that should appear in + * place of this one is not visible yet - we'll wait for it first. Otherwise - activity can be + * destroyed right away. + * @param reason Reason for finishing the activity. + * @return Flag indicating whether the activity was removed from history. + */ + ActivityRecord completeFinishing(String reason) { + if (!finishing || isState(RESUMED)) { + throw new IllegalArgumentException( + "Activity must be finishing and not resumed to complete, r=" + this + + ", finishing=" + finishing + ", state=" + mState); + } + + if (isState(PAUSING)) { + // Activity is marked as finishing and will be processed once it completes. + return this; + } - ActivityRecord finishCurrentActivityLocked(int mode, boolean oomAdj, String reason) { - // First things first: if this activity is currently visible, - // and the resumed activity is not yet visible, then hold off on - // finishing until the resumed one becomes visible. + boolean activityRemoved = false; + // If this activity is currently visible, and the resumed activity is not yet visible, then + // hold off on finishing until the resumed one becomes visible. // The activity that we are finishing may be over the lock screen. In this case, we do not // want to consider activities that cannot be shown on the lock screen as running and should // proceed with finishing the activity if there is no valid next top running activity. // Note that if this finishing activity is floating task, we don't need to wait the // next activity resume and can destroy it directly. + // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere + final ActivityRecord next = getDisplay().topRunningActivity( + true /* considerKeyguardState */); + final boolean isVisible = visible || nowVisible; final ActivityStack stack = getActivityStack(); - final ActivityDisplay display = getDisplay(); - final ActivityRecord next = display.topRunningActivity(true /* considerKeyguardState */); - final boolean isFloating = getConfiguration().windowConfiguration.tasksAreFloating(); - - if (mode == FINISH_AFTER_VISIBLE && (visible || nowVisible) - && next != null && !next.nowVisible && !isFloating) { - if (!mStackSupervisor.mStoppingActivities.contains(this)) { - stack.addToStopping(this, false /* scheduleIdle */, false /* idleDelayed */, - "finishCurrentActivityLocked"); - } + final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack(); + if (isVisible && next != null && !next.nowVisible) { + addToStopping(false /* scheduleIdle */, false /* idleDelayed */, + "completeFinishing"); if (DEBUG_STATES) { Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (finish requested)"); } - setState(STOPPING, "finishCurrentActivityLocked"); - if (oomAdj) { - mAtmService.updateOomAdj(); + setState(STOPPING, "completeFinishing"); + if (notFocusedStack) { + mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(), + false /* markFrozenIfConfigChanged */, true /* deferResume */); } - return this; + } else if (isVisible && isState(PAUSED) && getActivityStack().isFocusedStackOnDisplay() + && !inPinnedWindowingMode()) { + // TODO(b/137329632): Currently non-focused stack is handled differently. + addToFinishingAndWaitForIdle(); + } else { + // Not waiting for the next one to become visible - finish right away. + activityRemoved = destroyIfPossible(reason); } - // make sure the record is cleaned out of other places. + return activityRemoved ? null : this; + } + + /** + * Destroy and cleanup the activity both on client and server if possible. If activity is the + * last one left on display with home stack and there is no other running activity - delay + * destroying it until the next one starts. + */ + boolean destroyIfPossible(String reason) { + setState(FINISHING, "destroyIfPossible"); + + // Make sure the record is cleaned out of other places. mStackSupervisor.mStoppingActivities.remove(this); mStackSupervisor.mGoingToSleepActivities.remove(this); - final ActivityState prevState = getState(); - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + this); - - setState(FINISHING, "finishCurrentActivityLocked"); - - // Don't destroy activity immediately if the display contains home stack, although there is - // no next activity at the moment but another home activity should be started later. Keep - // this activity alive until next home activity is resumed then user won't see a temporary - // black screen. - final boolean noRunningStack = next == null && display.topRunningActivity() == null - && display.getHomeStack() == null; - final boolean noFocusedStack = stack != display.getFocusedStack(); - final boolean finishingInNonFocusedStackOrNoRunning = mode == FINISH_AFTER_VISIBLE - && prevState == PAUSED && (noFocusedStack || noRunningStack); - - if (mode == FINISH_IMMEDIATELY - || (prevState == PAUSED && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode())) - || finishingInNonFocusedStackOrNoRunning - || prevState == STARTED - || prevState == STOPPING - || prevState == STOPPED - || prevState == ActivityState.INITIALIZING) { - makeFinishingLocked(); - boolean activityRemoved = stack.destroyActivityLocked(this, true /* removeFromApp */, - "finish-imm:" + reason); - - if (finishingInNonFocusedStackOrNoRunning) { - // Finishing activity that was in paused state and it was in not currently focused - // stack, need to make something visible in its place. Also if the display does not - // have running activity, the configuration may need to be updated for restoring - // original orientation of the display. - mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(), - false /* markFrozenIfConfigChanged */, true /* deferResume */); - } - if (activityRemoved) { - mRootActivityContainer.resumeFocusedStacksTopActivities(); - } - if (DEBUG_CONTAINERS) { - Slog.d(TAG_CONTAINERS, "destroyActivityLocked: finishCurrentActivityLocked r=" - + this + " destroy returned removed=" + activityRemoved); - } - return activityRemoved ? null : this; + + final ActivityStack stack = getActivityStack(); + final ActivityDisplay display = getDisplay(); + // TODO(b/137329632): Exclude current activity when looking for the next one with + // ActivityDisplay#topRunningActivity(). + final ActivityRecord next = display.topRunningActivity(); + final boolean isLastStackOverEmptyHome = + next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null; + if (isLastStackOverEmptyHome) { + // Don't destroy activity immediately if this is the last activity on the display and + // the display contains home stack. Although there is no next activity at the moment, + // another home activity should be started later. Keep this activity alive until next + // home activity is resumed. This way the user won't see a temporary black screen. + addToFinishingAndWaitForIdle(); + return false; } + makeFinishingLocked(); - // Need to go through the full pause cycle to get this - // activity into the stopped state and then finish it. - if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + this); + final boolean activityRemoved = destroyImmediately(true /* removeFromApp */, + "finish-imm:" + reason); + + // If the display does not have running activity, the configuration may need to be + // updated for restoring original orientation of the display. + if (next == null) { + mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(), + false /* markFrozenIfConfigChanged */, true /* deferResume */); + } + if (activityRemoved) { + mRootActivityContainer.resumeFocusedStacksTopActivities(); + } + + if (DEBUG_CONTAINERS) { + Slog.d(TAG_CONTAINERS, "destroyIfPossible: r=" + this + " destroy returned removed=" + + activityRemoved); + } + + return activityRemoved; + } + + @VisibleForTesting + void addToFinishingAndWaitForIdle() { + if (DEBUG_STATES) Slog.v(TAG, "Enqueueing pending finish: " + this); + setState(FINISHING, "addToFinishingAndWaitForIdle"); mStackSupervisor.mFinishingActivities.add(this); resumeKeyDispatchingLocked(); mRootActivityContainer.resumeFocusedStacksTopActivities(); - // If activity was not paused at this point - explicitly pause it to start finishing - // process. Finishing will be completed once it reports pause back. - if (isState(RESUMED) && stack.mPausingActivity != null) { - stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, - next /* resuming */, false /* dontWait */); + } + + /** + * Destroy the current CLIENT SIDE instance of an activity. This may be called both when + * actually finishing an activity, or when performing a configuration switch where we destroy + * the current client-side object but then create a new client-side object for this same + * HistoryRecord. + * Normally the server-side record will be removed when the client reports back after + * destruction. If, however, at this point there is no client process attached, the record will + * removed immediately. + */ + boolean destroyImmediately(boolean removeFromApp, String reason) { + if (DEBUG_SWITCH || DEBUG_CLEANUP) { + Slog.v(TAG_SWITCH, "Removing activity from " + reason + ": token=" + this + + ", app=" + (hasProcess() ? app.mName : "(null)")); } - return this; + + if (isState(DESTROYING, DESTROYED)) { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "activity " + this + " already destroying." + + "skipping request with reason:" + reason); + } + return false; + } + + EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, mUserId, + System.identityHashCode(this), getTaskRecord().taskId, shortComponentName, reason); + + boolean removedFromHistory = false; + + cleanUp(false /* cleanServices */, false /* setState */); + + final ActivityStack stack = getActivityStack(); + final boolean hadApp = hasProcess(); + + if (hadApp) { + if (removeFromApp) { + app.removeActivity(this); + if (!app.hasActivities()) { + mAtmService.clearHeavyWeightProcessIfEquals(app); + // Update any services we are bound to that might care about whether + // their client may have activities. + // No longer have activities, so update LRU list and oom adj. + app.updateProcessInfo(true /* updateServiceConnectionActivities */, + false /* activityChange */, true /* updateOomAdj */); + } + } + + boolean skipDestroy = false; + + try { + if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + DestroyActivityItem.obtain(finishing, configChangeFlags)); + } catch (Exception e) { + // We can just ignore exceptions here... if the process has crashed, our death + // notification will clean things up. + if (finishing) { + removeFromHistory(reason + " exceptionInScheduleDestroy"); + removedFromHistory = true; + skipDestroy = true; + } + } + + nowVisible = false; + + // If the activity is finishing, we need to wait on removing it from the list to give it + // a chance to do its cleanup. During that time it may make calls back with its token + // so we need to be able to find it on the list and so we don't want to remove it from + // the list yet. Otherwise, we can just immediately put it in the destroyed state since + // we are not removing it from the list. + if (finishing && !skipDestroy) { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYING: " + this + " (destroy requested)"); + } + setState(DESTROYING, + "destroyActivityLocked. finishing and not skipping destroy"); + stack.scheduleDestroyTimeoutForActivity(this); + } else { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (destroy skipped)"); + } + setState(DESTROYED, + "destroyActivityLocked. not finishing or skipping destroy"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this); + app = null; + } + } else { + // Remove this record from the history. + if (finishing) { + removeFromHistory(reason + " hadNoApp"); + removedFromHistory = true; + } else { + if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (no app)"); + setState(DESTROYED, "destroyActivityLocked. not finishing and had no app"); + } + } + + configChangeFlags = 0; + + if (!stack.removeActivityFromLRUList(this) && hadApp) { + Slog.w(TAG, "Activity " + this + " being finished, but not in LRU list"); + } + + return removedFromHistory; + } + + boolean safelyDestroy(String reason) { + if (isDestroyable()) { + if (DEBUG_SWITCH) { + final ActivityStack stack = getActivityStack(); + Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState() + + " resumed=" + stack.mResumedActivity + + " pausing=" + stack.mPausingActivity + + " for reason " + reason); + } + return destroyImmediately(true /* removeFromApp */, reason); + } + return false; + } + + /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */ + void removeFromHistory(String reason) { + finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */); + makeFinishingLocked(); + if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE) { + Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + " from stack callers=" + + Debug.getCallers(5)); + } + + takeFromHistory(); + final ActivityStack stack = getActivityStack(); + stack.removeTimeoutsForActivity(this); + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (removed from history)"); + } + setState(DESTROYED, "removeFromHistory"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this); + app = null; + removeWindowContainer(); + final TaskRecord task = getTaskRecord(); + final boolean lastActivity = task.removeActivity(this); + // If we are removing the last activity in the task, not including task overlay activities, + // then fall through into the block below to remove the entire task itself + final boolean onlyHasTaskOverlays = + task.onlyHasTaskOverlayActivities(false /* excludingFinishing */); + + if (lastActivity || onlyHasTaskOverlays) { + if (DEBUG_STATES) { + Slog.i(TAG, "removeFromHistory: last activity removed from " + this + + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); + } + + // The following block can be executed multiple times if there is more than one overlay. + // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup + // of the task by id and exiting early if not found. + if (onlyHasTaskOverlays) { + // When destroying a task, tell the supervisor to remove it so that any activity it + // has can be cleaned up correctly. This is currently the only place where we remove + // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays + // state into removeTask(), we just clear the task here before the other residual + // work. + // TODO: If the callers to removeTask() changes such that we have multiple places + // where we are destroying the task, move this back into removeTask() + mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, + !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); + } + + // We must keep the task around until all activities are destroyed. The following + // statement will only execute once since overlays are also considered activities. + if (lastActivity) { + stack.removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING); + } + } + + cleanUpActivityServices(); + removeUriPermissionsLocked(); } void makeFinishingLocked() { @@ -1900,6 +2119,96 @@ final class ActivityRecord extends ConfigurationContainer { } } + /** + * This method is to only be called from the client via binder when the activity is destroyed + * AND finished. + */ + void destroyed(String reason) { + getActivityStack().removeDestroyTimeoutForActivity(this); + + if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + this); + + if (!isState(DESTROYING, DESTROYED)) { + throw new IllegalStateException( + "Reported destroyed for activity that is not destroying: r=" + this); + } + + if (isInStackLocked()) { + cleanUp(true /* cleanServices */, false /* setState */); + removeFromHistory(reason); + } + + mRootActivityContainer.resumeFocusedStacksTopActivities(); + } + + /** + * Perform the common clean-up of an activity record. This is called both as part of + * destroyActivityLocked() (when destroying the client-side representation) and cleaning things + * up as a result of its hosting processing going away, in which case there is no remaining + * client-side state to destroy so only the cleanup here is needed. + * + * Note: Call before {@link #removeFromHistory(String)}. + */ + void cleanUp(boolean cleanServices, boolean setState) { + final ActivityStack stack = getActivityStack(); + stack.onActivityRemovedFromStack(this); + + deferRelaunchUntilPaused = false; + frozenBeforeDestroy = false; + + if (setState) { + setState(DESTROYED, "cleanUp"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this); + app = null; + } + + // Inform supervisor the activity has been removed. + mStackSupervisor.cleanupActivity(this); + + // Remove any pending results. + if (finishing && pendingResults != null) { + for (WeakReference<PendingIntentRecord> apr : pendingResults) { + PendingIntentRecord rec = apr.get(); + if (rec != null) { + mAtmService.mPendingIntentController.cancelIntentSender(rec, + false /* cleanActivity */); + } + } + pendingResults = null; + } + + if (cleanServices) { + cleanUpActivityServices(); + } + + // Get rid of any pending idle timeouts. + stack.removeTimeoutsForActivity(this); + // Clean-up activities are no longer relaunching (e.g. app process died). Notify window + // manager so it can update its bookkeeping. + mAtmService.mWindowManager.notifyAppRelaunchesCleared(appToken); + } + + /** + * Perform clean-up of service connections in an activity record. + */ + private void cleanUpActivityServices() { + if (mServiceConnectionsHolder == null) { + return; + } + // Throw away any services that have been bound by this activity. + mServiceConnectionsHolder.disconnectActivityFromServices(); + } + + void logStartActivity(int tag, TaskRecord task) { + final Uri data = intent.getData(); + final String strData = data != null ? data.toSafeString() : null; + + EventLog.writeEvent(tag, + mUserId, System.identityHashCode(this), task.taskId, + shortComponentName, intent.getAction(), + intent.getType(), strData, intent.getFlags()); + } + UriPermissionOwner getUriPermissionsLocked() { if (uriPermissions == null) { uriPermissions = new UriPermissionOwner(mAtmService.mUgmInternal, this); @@ -1936,6 +2245,33 @@ final class ActivityRecord extends ConfigurationContainer { } } + void sendResult(int callingUid, String resultWho, int requestCode, int resultCode, + Intent data) { + if (callingUid > 0) { + mAtmService.mUgmInternal.grantUriPermissionFromIntent(callingUid, packageName, + data, getUriPermissionsLocked(), mUserId); + } + + if (DEBUG_RESULTS) { + Slog.v(TAG, "Send activity result to " + this + + " : who=" + resultWho + " req=" + requestCode + + " res=" + resultCode + " data=" + data); + } + if (isState(RESUMED) && attachedToProcess()) { + try { + final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); + list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + ActivityResultItem.obtain(list)); + return; + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending result to " + this, e); + } + } + + addResultLocked(null /* from */, resultWho, requestCode, resultCode, data); + } + private void addNewIntentLocked(ReferrerIntent intent) { if (newIntents == null) { newIntents = new ArrayList<>(); @@ -2415,6 +2751,65 @@ final class ActivityRecord extends ConfigurationContainer { } } + void makeInvisible() { + if (!visible) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this); + return; + } + // Now for any activities that aren't visible to the user, make sure they no longer are + // keeping the screen frozen. + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState()); + } + try { + final boolean canEnterPictureInPicture = checkEnterPictureInPictureState( + "makeInvisible", true /* beforeStopping */); + // Defer telling the client it is hidden if it can enter Pip and isn't current paused, + // stopped or stopping. This gives it a chance to enter Pip in onPause(). + // TODO: There is still a question surrounding activities in multi-window mode that want + // to enter Pip after they are paused, but are still visible. I they should be okay to + // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and + // the current contract for "auto-Pip" is that the app should enter it before onPause + // returns. Just need to confirm this reasoning makes sense. + final boolean deferHidingClient = canEnterPictureInPicture + && !isState(STOPPING, STOPPED, PAUSED); + setDeferHidingClient(deferHidingClient); + setVisible(false); + + switch (getState()) { + case STOPPING: + case STOPPED: + if (attachedToProcess()) { + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this); + } + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + appToken, WindowVisibilityItem.obtain(false /* showWindow */)); + } + + // Reset the flag indicating that an app can enter picture-in-picture once the + // activity is hidden + supportsEnterPipOnTaskSwitch = false; + break; + + case INITIALIZING: + case RESUMED: + case PAUSING: + case PAUSED: + case STARTED: + addToStopping(true /* scheduleIdle */, + canEnterPictureInPicture /* idleDelayed */, "makeInvisible"); + break; + + default: + break; + } + } catch (Exception e) { + // Just skip on any failure; we'll make it visible when it next restarts. + Slog.w(TAG, "Exception thrown making hidden: " + intent.getComponent(), e); + } + } + /** * Make activity resumed or paused if needed. * @param activeActivity an activity that is resumed or just completed pause action. @@ -2605,13 +3000,75 @@ final class ActivityRecord extends ConfigurationContainer { } } + void stopIfPossible() { + if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this); + final ActivityStack stack = getActivityStack(); + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 + || (info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0) { + if (!finishing) { + if (!stack.shouldSleepActivities()) { + if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this); + if (finishIfPossible("stop-no-history", false /* oomAdj */) + != FINISH_RESULT_CANCELLED) { + // {@link adjustFocusedActivityStack} must have been already called. + resumeKeyDispatchingLocked(); + return; + } + } else { + if (DEBUG_STATES) { + Slog.d(TAG_STATES, "Not finishing noHistory " + this + + " on stop because we're just sleeping"); + } + } + } + } + + if (!attachedToProcess()) { + return; + } + stack.adjustFocusedActivityStack(this, "stopActivity"); + resumeKeyDispatchingLocked(); + try { + stopped = false; + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (stop requested)"); + } + setState(STOPPING, "stopIfPossible"); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Stopping visible=" + visible + " for " + this); + } + if (!visible) { + setVisible(false); + } + EventLogTags.writeAmStopActivity( + mUserId, System.identityHashCode(this), shortComponentName); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + StopActivityItem.obtain(visible, configChangeFlags)); + if (stack.shouldSleepOrShutDownActivities()) { + setSleeping(true); + } + stack.scheduleStopTimeoutForActivity(this); + } catch (Exception e) { + // Maybe just ignore exceptions here... if the process has crashed, our death + // notification will clean things up. + Slog.w(TAG, "Exception thrown during pause", e); + // Just in case, assume it to be stopped. + stopped = true; + if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + this); + setState(STOPPED, "stopIfPossible"); + if (deferRelaunchUntilPaused) { + destroyImmediately(true /* removeFromApp */, "stop-except"); + } + } + } + final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState, CharSequence description) { final ActivityStack stack = getActivityStack(); final boolean isStopping = mState == STOPPING; if (!isStopping && mState != RESTARTING_PROCESS) { Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this); - stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); + stack.removeStopTimeoutForActivity(this); return; } if (newPersistentState != null) { @@ -2629,7 +3086,7 @@ final class ActivityRecord extends ConfigurationContainer { if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + mIcicle); if (!stopped) { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)"); - stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); + stack.removeStopTimeoutForActivity(this); stopped = true; if (isStopping) { setState(STOPPED, "activityStoppedLocked"); @@ -2643,7 +3100,7 @@ final class ActivityRecord extends ConfigurationContainer { clearOptionsLocked(); } else { if (deferRelaunchUntilPaused) { - stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config"); + destroyImmediately(true /* removeFromApp */, "stop-config"); mRootActivityContainer.resumeFocusedStacksTopActivities(); } else { mRootActivityContainer.updatePreviousProcess(this); @@ -2652,6 +3109,34 @@ final class ActivityRecord extends ConfigurationContainer { } } + void addToStopping(boolean scheduleIdle, boolean idleDelayed, String reason) { + if (!mStackSupervisor.mStoppingActivities.contains(this)) { + EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, mUserId, + System.identityHashCode(this), shortComponentName, reason); + mStackSupervisor.mStoppingActivities.add(this); + } + + final ActivityStack stack = getActivityStack(); + // If we already have a few activities waiting to stop, then give up on things going idle + // and start clearing them out. Or if r is the last of activity of the last task the stack + // will be empty and must be cleared immediately. + boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE + || (isRootOfTask() && stack.getChildCount() <= 1); + if (scheduleIdle || forceIdle) { + if (DEBUG_PAUSE) { + Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" + forceIdle + + "immediate=" + !idleDelayed); + } + if (!idleDelayed) { + mStackSupervisor.scheduleIdleLocked(); + } else { + mStackSupervisor.scheduleIdleTimeoutLocked(this); + } + } else { + stack.checkReadyForSleep(); + } + } + void startLaunchTickingLocked() { if (Build.IS_USER) { return; @@ -2672,18 +3157,18 @@ final class ActivityRecord extends ConfigurationContainer { return false; } - Message msg = stack.mHandler.obtainMessage(LAUNCH_TICK_MSG, this); - stack.mHandler.removeMessages(LAUNCH_TICK_MSG); - stack.mHandler.sendMessageDelayed(msg, LAUNCH_TICK); + stack.removeLaunchTickMessages(); + stack.scheduleLaunchTickForActivity(this); return true; } void finishLaunchTickingLocked() { launchTickTime = 0; final ActivityStack stack = getActivityStack(); - if (stack != null) { - stack.mHandler.removeMessages(LAUNCH_TICK_MSG); + if (stack == null) { + return; } + stack.removeLaunchTickMessages(); } // IApplicationToken @@ -3660,7 +4145,7 @@ final class ActivityRecord extends ConfigurationContainer { if (!attachedToProcess()) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is destroying non-running " + this); - stack.destroyActivityLocked(this, true, "config"); + destroyImmediately(true /* removeFromApp */, "config"); } else if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. @@ -3844,7 +4329,7 @@ final class ActivityRecord extends ConfigurationContainer { } else { final ActivityStack stack = getActivityStack(); if (stack != null) { - stack.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); + stack.removePauseTimeoutForActivity(this); } setState(PAUSED, "relaunchActivityLocked"); } @@ -3884,7 +4369,7 @@ final class ActivityRecord extends ConfigurationContainer { if (!visible || mHaveState) { // Kill its process immediately because the activity should be in background. // The activity state will be update to {@link #DESTROYED} in - // {@link ActivityStack#cleanUpActivityLocked} when handling process died. + // {@link ActivityStack#cleanUp} when handling process died. mAtmService.mH.post(() -> { final WindowProcessController wpc; synchronized (mAtmService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index daf32862e287..8bdedffa581a 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -56,12 +56,7 @@ import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY; import static com.android.server.am.ActivityStackProto.TASKS; import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; -import static com.android.server.wm.ActivityRecord.FINISH_AFTER_VISIBLE; -import static com.android.server.wm.ActivityRecord.FINISH_IMMEDIATELY; -import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; -import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; -import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; @@ -70,14 +65,12 @@ import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; -import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityStackSupervisor.printThisActivity; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; @@ -91,7 +84,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBIL import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; @@ -108,6 +100,7 @@ import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.RootActivityContainer.FindTaskResult; +import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; import static java.lang.Integer.MAX_VALUE; @@ -124,12 +117,9 @@ import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.ResumeActivityItem; -import android.app.servertransaction.StopActivityItem; -import android.app.servertransaction.WindowVisibilityItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -164,11 +154,9 @@ import com.android.server.am.ActivityManagerService; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.AppTimeTracker; import com.android.server.am.EventLogTags; -import com.android.server.am.PendingIntentRecord; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -182,7 +170,6 @@ class ActivityStack extends ConfigurationContainer { private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_APP = TAG + POSTFIX_APP; private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; - private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; @@ -195,7 +182,7 @@ class ActivityStack extends ConfigurationContainer { private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; // Ticks during which we check progress while waiting for an app to launch. - static final int LAUNCH_TICK = 500; + private static final int LAUNCH_TICK = 500; // How long we wait until giving up on the last activity to pause. This // is short because it directly impacts the responsiveness of starting the @@ -223,9 +210,6 @@ class ActivityStack extends ConfigurationContainer { // convertToTranslucent(). private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000; - // How many activities have to be scheduled to stop to force a stop pass. - private static final int MAX_STOPPING_TO_FORCE = 3; - @IntDef(prefix = {"STACK_VISIBILITY"}, value = { STACK_VISIBILITY_VISIBLE, STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -412,12 +396,12 @@ class ActivityStack extends ConfigurationContainer { private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; - static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; - static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2; - static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3; - static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4; - static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5; - static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6; + private static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; + private static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2; + private static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3; + private static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4; + private static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5; + private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6; // TODO: remove after unification. TaskStack mTaskStack; @@ -431,7 +415,7 @@ class ActivityStack extends ConfigurationContainer { } } - final Handler mHandler; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -468,7 +452,9 @@ class ActivityStack extends ConfigurationContainer { // so we need to be conservative and assume it isn't. Slog.w(TAG, "Activity destroy timeout for " + r); synchronized (mService.mGlobalLock) { - activityDestroyedLocked(r != null ? r.appToken : null, "destroyTimeout"); + if (r != null) { + r.destroyed("destroyTimeout"); + } } } break; case STOP_TIMEOUT_MSG: { @@ -1215,12 +1201,17 @@ class ActivityStack extends ConfigurationContainer { return display != null && display.isSingleTaskInstance(); } - final void removeActivitiesFromLRUListLocked(TaskRecord task) { + private void removeActivitiesFromLRUList(TaskRecord task) { for (ActivityRecord r : task.mActivities) { mLRUActivities.remove(r); } } + /** @return {@code true} if LRU list contained the specified activity. */ + final boolean removeActivityFromLRUList(ActivityRecord activity) { + return mLRUActivities.remove(activity); + } + final boolean updateLRUListLocked(ActivityRecord r) { final boolean hadit = mLRUActivities.remove(r); mLRUActivities.add(r); @@ -1617,18 +1608,6 @@ class ActivityStack extends ConfigurationContainer { } /** - * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because - * this directly impacts the responsiveness seen by the user. - */ - private void schedulePauseTimeout(ActivityRecord r) { - final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); - msg.obj = r; - r.pauseTime = SystemClock.uptimeMillis(); - mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete..."); - } - - /** * Start pausing the currently resumed activity. It is an error to call this if there * is already an activity being paused or there is no resumed activity. * @@ -1728,7 +1707,7 @@ class ActivityStack extends ConfigurationContainer { return false; } else { - schedulePauseTimeout(prev); + schedulePauseTimeoutForActivity(prev); return true; } @@ -1771,8 +1750,7 @@ class ActivityStack extends ConfigurationContainer { if (r.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of failed to pause activity: " + r); - r.finishCurrentActivityLocked(FINISH_AFTER_VISIBLE, false, - "activityPausedLocked"); + r.completeFinishing("activityPausedLocked"); } } } @@ -1790,8 +1768,7 @@ class ActivityStack extends ConfigurationContainer { prev.setState(PAUSED, "completePausedLocked"); if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev); - prev = prev.finishCurrentActivityLocked(FINISH_AFTER_VISIBLE, false /* oomAdj */, - "completePausedLocked"); + prev = prev.completeFinishing("completePausedLocked"); } else if (prev.hasProcess()) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev + " wasStopping=" + wasStopping + " visible=" + prev.visible); @@ -1810,7 +1787,7 @@ class ActivityStack extends ConfigurationContainer { prev.setDeferHidingClient(false); // If we were visible then resumeTopActivities will release resources before // stopping. - addToStopping(prev, true /* scheduleIdle */, false /* idleDelayed */, + prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */, "completePauseLocked"); } } else { @@ -1872,32 +1849,6 @@ class ActivityStack extends ConfigurationContainer { mRootActivityContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); } - void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed, String reason) { - if (!mStackSupervisor.mStoppingActivities.contains(r)) { - EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, r.mUserId, - System.identityHashCode(r), r.shortComponentName, reason); - mStackSupervisor.mStoppingActivities.add(r); - } - - // If we already have a few activities waiting to stop, then give up - // on things going idle and start clearing them out. Or if r is the - // last of activity of the last task the stack will be empty and must - // be cleared immediately. - boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE - || (r.isRootOfTask() && mTaskHistory.size() <= 1); - if (scheduleIdle || forceIdle) { - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" - + forceIdle + "immediate=" + !idleDelayed); - if (!idleDelayed) { - mStackSupervisor.scheduleIdleLocked(); - } else { - mStackSupervisor.scheduleIdleTimeoutLocked(r); - } - } else { - checkReadyForSleep(); - } - } - /** * Returns true if the stack is translucent and can have other contents visible behind it if * needed. A stack is considered translucent if it don't contain a visible or @@ -2207,7 +2158,7 @@ class ActivityStack extends ConfigurationContainer { + " stackShouldBeVisible=" + stackShouldBeVisible + " behindFullscreenActivity=" + behindFullscreenActivity + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); - makeInvisible(r); + r.makeInvisible(); } } final int windowingMode = getWindowingMode(); @@ -2384,63 +2335,6 @@ class ActivityStack extends ConfigurationContainer { return false; } - // TODO: Should probably be moved into ActivityRecord. - private void makeInvisible(ActivityRecord r) { - if (!r.visible) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); - return; - } - // Now for any activities that aren't visible to the user, make sure they no longer are - // keeping the screen frozen. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.getState()); - try { - final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState( - "makeInvisible", true /* beforeStopping */); - // Defer telling the client it is hidden if it can enter Pip and isn't current paused, - // stopped or stopping. This gives it a chance to enter Pip in onPause(). - // TODO: There is still a question surrounding activities in multi-window mode that want - // to enter Pip after they are paused, but are still visible. I they should be okay to - // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and - // the current contract for "auto-Pip" is that the app should enter it before onPause - // returns. Just need to confirm this reasoning makes sense. - final boolean deferHidingClient = canEnterPictureInPicture - && !r.isState(STOPPING, STOPPED, PAUSED); - r.setDeferHidingClient(deferHidingClient); - r.setVisible(false); - - switch (r.getState()) { - case STOPPING: - case STOPPED: - if (r.attachedToProcess()) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Scheduling invisibility: " + r); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), - r.appToken, WindowVisibilityItem.obtain(false /* showWindow */)); - } - - // Reset the flag indicating that an app can enter picture-in-picture once the - // activity is hidden - r.supportsEnterPipOnTaskSwitch = false; - break; - - case INITIALIZING: - case RESUMED: - case PAUSING: - case PAUSED: - case STARTED: - addToStopping(r, true /* scheduleIdle */, - canEnterPictureInPicture /* idleDelayed */, "makeInvisible"); - break; - - default: - break; - } - } catch (Exception e) { - // Just skip on any failure; we'll make it visible when it next restarts. - Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e); - } - } - private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity, ActivityRecord r) { if (r.fullscreen) { @@ -2782,8 +2676,7 @@ class ActivityStack extends ConfigurationContainer { !mLastNoHistoryActivity.finishing) { if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + mLastNoHistoryActivity + " on new resume"); - mLastNoHistoryActivity.finishActivityLocked(Activity.RESULT_CANCELED, - null /* resultData */, "resume-no-history", false /* oomAdj */); + mLastNoHistoryActivity.finishIfPossible("resume-no-history", false /* oomAdj */); mLastNoHistoryActivity = null; } @@ -3018,8 +2911,7 @@ class ActivityStack extends ConfigurationContainer { // If any exception gets thrown, toss away this // activity and try the next one. Slog.w(TAG, "Exception thrown during resume of " + next, e); - next.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "resume-exception", true /* oomAdj */); + next.finishIfPossible("resume-exception", true /* oomAdj */); return true; } } else { @@ -3322,6 +3214,7 @@ class ActivityStack extends ConfigurationContainer { // to activities in the same chain will depend on what the end activity of the chain needs. int replyChainEnd = -1; boolean canMoveOptions = true; + int numTasksCreated = 0; // We only do this for activities that are not the root of the task (since if we finish // the root, we may no longer have the task!). @@ -3386,6 +3279,7 @@ class ActivityStack extends ConfigurationContainer { target.info, null /* intent */, null /* voiceSession */, null /* voiceInteractor */, false /* toTop */); targetTask.affinityIntent = target.intent; + numTasksCreated++; if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target + " out to new task " + targetTask); } @@ -3414,6 +3308,7 @@ class ActivityStack extends ConfigurationContainer { } positionChildWindowContainerAtBottom(targetTask); + mStackSupervisor.mRecentTasks.add(targetTask); replyChainEnd = -1; } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -3446,8 +3341,8 @@ class ActivityStack extends ConfigurationContainer { } if (DEBUG_TASKS) Slog.w(TAG_TASKS, "resetTaskIntendedTask: calling finishActivity on " + p); - if (p.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "reset-task", false /* oomAdj */) == FINISH_RESULT_REMOVED) { + if (p.finishIfPossible("reset-task", false /* oomAdj */) + == FINISH_RESULT_REMOVED) { end--; srcPos--; } @@ -3461,6 +3356,27 @@ class ActivityStack extends ConfigurationContainer { } } + // Create target stack for the newly created tasks if necessary + if (numTasksCreated > 0) { + ActivityDisplay display = getDisplay(); + final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); + if (singleTaskInstanceDisplay) { + display = mRootActivityContainer.getDefaultDisplay(); + } + + if (singleTaskInstanceDisplay || display.alwaysCreateStack(getWindowingMode(), + getActivityType())) { + for (int index = numTasksCreated - 1; index >= 0; index--) { + final TaskRecord targetTask = mTaskHistory.get(index); + final ActivityStack targetStack = display.getOrCreateStack(getWindowingMode(), + getActivityType(), false /* onTop */); + targetTask.reparent(targetStack, false /* toTop */, + REPARENT_LEAVE_STACK_IN_PLACE, false /* animate */, + true /* deferResume */, "resetTargetTask"); + } + } + } + return topOptions; } @@ -3531,8 +3447,7 @@ class ActivityStack extends ConfigurationContainer { if (p.finishing) { continue; } - p.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "move-affinity", false /* oomAdj */); + p.finishIfPossible("move-affinity", false /* oomAdj */); } } else { if (taskInsertionPoint < 0) { @@ -3566,8 +3481,7 @@ class ActivityStack extends ConfigurationContainer { if (targetNdx > 0) { final ActivityRecord p = taskActivities.get(targetNdx - 1); if (p.intent.getComponent().equals(target.intent.getComponent())) { - p.finishActivityLocked(Activity.RESULT_CANCELED, - null /* resultData */, "replace", false /* oomAdj */); + p.finishIfPossible("replace", false /* oomAdj */); } } } @@ -3579,6 +3493,16 @@ class ActivityStack extends ConfigurationContainer { return taskInsertionPoint; } + /** + * Reset the task by reparenting the activities that have same affinity to the task or + * reparenting the activities that have different affinityies out of the task, while these + * activities allow task reparenting. + * + * @param taskTop Top activity of the task might be reset. + * @param newActivity The activity that going to be started. + * @return The non-finishing top activity of the task after reset or the original task top + * activity if all activities within the task are finishing. + */ final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, ActivityRecord newActivity) { final boolean forceReset = @@ -3609,9 +3533,10 @@ class ActivityStack extends ConfigurationContainer { int taskNdx = mTaskHistory.indexOf(task); if (taskNdx >= 0) { - do { - taskTop = mTaskHistory.get(taskNdx--).getTopActivity(); - } while (taskTop == null && taskNdx >= 0); + ActivityRecord newTop = mTaskHistory.get(taskNdx).getTopActivity(); + if (newTop != null) { + taskTop = newTop; + } } if (topOptions != null) { @@ -3627,49 +3552,6 @@ class ActivityStack extends ConfigurationContainer { return taskTop; } - void sendActivityResultLocked(int callingUid, ActivityRecord r, - String resultWho, int requestCode, int resultCode, Intent data) { - - if (callingUid > 0) { - mService.mUgmInternal.grantUriPermissionFromIntent(callingUid, r.packageName, - data, r.getUriPermissionsLocked(), r.mUserId); - } - - if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r - + " : who=" + resultWho + " req=" + requestCode - + " res=" + resultCode + " data=" + data); - if (mResumedActivity == r && r.attachedToProcess()) { - try { - ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); - list.add(new ResultInfo(resultWho, requestCode, - resultCode, data)); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - ActivityResultItem.obtain(list)); - return; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending result to " + r, e); - } - } - - r.addResultLocked(null, resultWho, requestCode, resultCode, data); - } - - /** Returns true if the task is one of the task finishing on-top of the top running task. */ - private boolean isATopFinishingTask(TaskRecord task) { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord current = mTaskHistory.get(i); - final ActivityRecord r = current.topRunningActivityLocked(); - if (r != null) { - // We got a top running activity, so there isn't a top finishing task... - return false; - } - if (current == task) { - return true; - } - } - return false; - } - void adjustFocusedActivityStack(ActivityRecord r, String reason) { if (!mRootActivityContainer.isTopDisplayFocusedStack(this) || ((mResumedActivity != r) && (mResumedActivity != null))) { @@ -3748,64 +3630,6 @@ class ActivityStack extends ConfigurationContainer { return stack; } - final void stopActivityLocked(ActivityRecord r) { - if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 - || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { - if (!r.finishing) { - if (!shouldSleepActivities()) { - if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + r); - if (r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "stop-no-history", false /* oomAdj */) != FINISH_RESULT_CANCELLED) { - // {@link adjustFocusedActivityStack} must have been already called. - r.resumeKeyDispatchingLocked(); - return; - } - } else { - if (DEBUG_STATES) Slog.d(TAG_STATES, "Not finishing noHistory " + r - + " on stop because we're just sleeping"); - } - } - } - - if (r.attachedToProcess()) { - adjustFocusedActivityStack(r, "stopActivity"); - r.resumeKeyDispatchingLocked(); - try { - r.stopped = false; - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Moving to STOPPING: " + r + " (stop requested)"); - r.setState(STOPPING, "stopActivityLocked"); - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Stopping visible=" + r.visible + " for " + r); - if (!r.visible) { - r.setVisible(false); - } - EventLogTags.writeAmStopActivity( - r.mUserId, System.identityHashCode(r), r.shortComponentName); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - StopActivityItem.obtain(r.visible, r.configChangeFlags)); - if (shouldSleepOrShutDownActivities()) { - r.setSleeping(true); - } - Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); - mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); - } catch (Exception e) { - // Maybe just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - Slog.w(TAG, "Exception thrown during pause", e); - // Just in case, assume it to be stopped. - r.stopped = true; - if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + r); - r.setState(STOPPED, "stopActivityLocked"); - if (r.deferRelaunchUntilPaused) { - destroyActivityLocked(r, true, "stop-except"); - } - } - } - } - /** Finish all activities that were started for result from the specified activity. */ final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -3815,8 +3639,7 @@ class ActivityStack extends ConfigurationContainer { if (r.resultTo == self && r.requestCode == requestCode) { if ((r.resultWho == null && resultWho == null) || (r.resultWho != null && r.resultWho.equals(resultWho))) { - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "request-sub", false /* oomAdj */); + r.finishIfPossible("request-sub", false /* oomAdj */); } } } @@ -3846,8 +3669,7 @@ class ActivityStack extends ConfigurationContainer { int activityNdx = task.mActivities.indexOf(r); getDisplay().mDisplayContent.prepareAppTransition( TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, reason, - false /* oomAdj */); + r.finishIfPossible(reason, false /* oomAdj */); finishedTask = task; // Also terminate any activities below it that aren't yet // stopped, to avoid a situation where one will get @@ -3868,8 +3690,7 @@ class ActivityStack extends ConfigurationContainer { if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, reason, - false /* oomAdj */); + r.finishIfPossible(reason, false /* oomAdj */); } } } @@ -3885,8 +3706,7 @@ class ActivityStack extends ConfigurationContainer { for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = tr.mActivities.get(activityNdx); if (!r.finishing) { - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "finish-voice", false /* oomAdj */); + r.finishIfPossible("finish-voice", false /* oomAdj */); didOne = true; } } @@ -3924,8 +3744,7 @@ class ActivityStack extends ConfigurationContainer { final ActivityRecord r = activities.get(activityNdx); noActivitiesInStack = false; Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r); - r.finishCurrentActivityLocked(FINISH_IMMEDIATELY, false /* oomAdj */, - "finishAllActivitiesImmediatelyLocked"); + r.destroyIfPossible("finishAllActivitiesImmediatelyLocked"); } } if (noActivitiesInStack) { @@ -4029,7 +3848,8 @@ class ActivityStack extends ConfigurationContainer { final long origId = Binder.clearCallingIdentity(); for (int i = start; i > finishTo; i--) { final ActivityRecord r = activities.get(i); - r.finishActivityLocked(resultCode, resultData, "navigate-up", true /* oomAdj */); + r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */, + !PAUSE_IMMEDIATELY); // Only return the supplied result for the first activity finished resultCode = Activity.RESULT_CANCELED; resultData = null; @@ -4066,8 +3886,8 @@ class ActivityStack extends ConfigurationContainer { } catch (RemoteException e) { foundParentInTask = false; } - parent.finishActivityLocked(resultCode, resultData, "navigate-top", - true /* oomAdj */); + parent.finishIfPossible(resultCode, resultData, "navigate-top", + true /* oomAdj */, !PAUSE_IMMEDIATELY); } } Binder.restoreCallingIdentity(origId); @@ -4079,7 +3899,7 @@ class ActivityStack extends ConfigurationContainer { * an activity moves away from the stack. */ void onActivityRemovedFromStack(ActivityRecord r) { - removeTimeoutsForActivityLocked(r); + removeTimeoutsForActivity(r); if (mResumedActivity != null && mResumedActivity == r) { setResumedActivity(null, "onActivityRemovedFromStack"); @@ -4095,132 +3915,64 @@ class ActivityStack extends ConfigurationContainer { } } - /** - * Perform the common clean-up of an activity record. This is called both - * as part of destroyActivityLocked() (when destroying the client-side - * representation) and cleaning things up as a result of its hosting - * processing going away, in which case there is no remaining client-side - * state to destroy so only the cleanup here is needed. - * - * Note: Call before #removeActivityFromHistoryLocked. - */ - private void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) { - onActivityRemovedFromStack(r); - - r.deferRelaunchUntilPaused = false; - r.frozenBeforeDestroy = false; - - if (setState) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (cleaning up)"); - r.setState(DESTROYED, "cleanupActivityLocked"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + r); - r.app = null; - } - - // Inform supervisor the activity has been removed. - mStackSupervisor.cleanupActivity(r); - - - // Remove any pending results. - if (r.finishing && r.pendingResults != null) { - for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { - PendingIntentRecord rec = apr.get(); - if (rec != null) { - mService.mPendingIntentController.cancelIntentSender(rec, false); - } - } - r.pendingResults = null; - } + /// HANDLER INTERFACE BEGIN + void removeTimeoutsForActivity(ActivityRecord r) { + mStackSupervisor.removeTimeoutsForActivityLocked(r); + removePauseTimeoutForActivity(r); + removeStopTimeoutForActivity(r); + removeDestroyTimeoutForActivity(r); + r.finishLaunchTickingLocked(); + } - if (cleanServices) { - cleanUpActivityServicesLocked(r); - } + void scheduleDestroyActivities(WindowProcessController owner, String reason) { + final Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG); + msg.obj = new ScheduleDestroyArgs(owner, reason); + mHandler.sendMessage(msg); + } - // Get rid of any pending idle timeouts. - removeTimeoutsForActivityLocked(r); - // Clean-up activities are no longer relaunching (e.g. app process died). Notify window - // manager so it can update its bookkeeping. - mWindowManager.notifyAppRelaunchesCleared(r.appToken); + void scheduleDestroyTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r); + mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); } - private void removeTimeoutsForActivityLocked(ActivityRecord r) { - mStackSupervisor.removeTimeoutsForActivityLocked(r); - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - mHandler.removeMessages(STOP_TIMEOUT_MSG, r); + void removeDestroyTimeoutForActivity(ActivityRecord r) { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); - r.finishLaunchTickingLocked(); } - private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) { - r.finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */); - r.makeFinishingLocked(); - if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, - "Removing activity " + r + " from stack callers=" + Debug.getCallers(5)); - - r.takeFromHistory(); - removeTimeoutsForActivityLocked(r); - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Moving to DESTROYED: " + r + " (removed from history)"); - r.setState(DESTROYED, "removeActivityFromHistoryLocked"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + r); - r.app = null; - r.removeWindowContainer(); - final TaskRecord task = r.getTaskRecord(); - final boolean lastActivity = task != null ? task.removeActivity(r) : false; - // If we are removing the last activity in the task, not including task overlay activities, - // then fall through into the block below to remove the entire task itself - final boolean onlyHasTaskOverlays = task != null - ? task.onlyHasTaskOverlayActivities(false /* excludingFinishing */) : false; - - if (lastActivity || onlyHasTaskOverlays) { - if (DEBUG_STACK) { - Slog.i(TAG_STACK, - "removeActivityFromHistoryLocked: last activity removed from " + this - + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); - } - - // The following block can be executed multiple times if there is more than one overlay. - // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup - // of the task by id and exiting early if not found. - if (onlyHasTaskOverlays) { - // When destroying a task, tell the supervisor to remove it so that any activity it - // has can be cleaned up correctly. This is currently the only place where we remove - // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays - // state into removeTask(), we just clear the task here before the other residual - // work. - // TODO: If the callers to removeTask() changes such that we have multiple places - // where we are destroying the task, move this back into removeTask() - mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, - !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); - } + void scheduleStopTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); + mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); + } - // We must keep the task around until all activities are destroyed. The following - // statement will only execute once since overlays are also considered activities. - if (lastActivity) { - removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING); - } - } - cleanUpActivityServicesLocked(r); - r.removeUriPermissionsLocked(); + void removeStopTimeoutForActivity(ActivityRecord r) { + mHandler.removeMessages(STOP_TIMEOUT_MSG, r); } /** - * Perform clean-up of service connections in an activity record. + * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because + * this directly impacts the responsiveness seen by the user. */ - private void cleanUpActivityServicesLocked(ActivityRecord r) { - if (r.mServiceConnectionsHolder == null) { - return; - } - // Throw away any services that have been bound by this activity. - r.mServiceConnectionsHolder.disconnectActivityFromServices(); + private void schedulePauseTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG, r); + r.pauseTime = SystemClock.uptimeMillis(); + mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete..."); } - final void scheduleDestroyActivities(WindowProcessController owner, String reason) { - Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG); - msg.obj = new ScheduleDestroyArgs(owner, reason); - mHandler.sendMessage(msg); + void removePauseTimeoutForActivity(ActivityRecord r) { + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + } + + void scheduleLaunchTickForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(LAUNCH_TICK_MSG, r); + mHandler.sendMessageDelayed(msg, LAUNCH_TICK); } + void removeLaunchTickMessages() { + mHandler.removeMessages(LAUNCH_TICK_MSG); + } + /// HANDLER INTERFACE END + private void destroyActivitiesLocked(WindowProcessController owner, String reason) { boolean lastIsOpaque = false; boolean activityRemoved = false; @@ -4245,7 +3997,7 @@ class ActivityStack extends ConfigurationContainer { + " in state " + r.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); - if (destroyActivityLocked(r, true, reason)) { + if (r.destroyImmediately(true /* removeFromTask */, reason)) { activityRemoved = true; } } @@ -4256,16 +4008,6 @@ class ActivityStack extends ConfigurationContainer { } } - final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) { - if (r.isDestroyable()) { - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Destroying " + r + " in state " + r.getState() + " resumed=" + mResumedActivity - + " pausing=" + mPausingActivity + " for reason " + reason); - return destroyActivityLocked(r, true, reason); - } - return false; - } - final int releaseSomeActivitiesLocked(WindowProcessController app, ArraySet<TaskRecord> tasks, String reason) { // Iterate over tasks starting at the back (oldest) first. @@ -4289,7 +4031,7 @@ class ActivityStack extends ConfigurationContainer { if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity + " in state " + activity.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); - destroyActivityLocked(activity, true, reason); + activity.destroyImmediately(true /* removeFromApp */, reason); if (activities.get(actNdx) != activity) { // Was removed from list, back up so we don't miss the next one. actNdx--; @@ -4311,142 +4053,6 @@ class ActivityStack extends ConfigurationContainer { return numReleased; } - /** - * Destroy the current CLIENT SIDE instance of an activity. This may be - * called both when actually finishing an activity, or when performing - * a configuration switch where we destroy the current client-side object - * but then create a new client-side object for this same HistoryRecord. - */ - final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) { - if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH, - "Removing activity from " + reason + ": token=" + r - + ", app=" + (r.hasProcess() ? r.app.mName : "(null)")); - - if (r.isState(DESTROYING, DESTROYED)) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "activity " + r + " already destroying." - + "skipping request with reason:" + reason); - return false; - } - - EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, - r.mUserId, System.identityHashCode(r), - r.getTaskRecord().taskId, r.shortComponentName, reason); - - boolean removedFromHistory = false; - - cleanUpActivityLocked(r, false, false); - - final boolean hadApp = r.hasProcess(); - - if (hadApp) { - if (removeFromApp) { - r.app.removeActivity(r); - if (!r.app.hasActivities()) { - mService.clearHeavyWeightProcessIfEquals(r.app); - } - if (!r.app.hasActivities()) { - // Update any services we are bound to that might care about whether - // their client may have activities. - // No longer have activities, so update LRU list and oom adj. - r.app.updateProcessInfo(true /* updateServiceConnectionActivities */, - false /* activityChange */, true /* updateOomAdj */); - } - } - - boolean skipDestroy = false; - - try { - if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - DestroyActivityItem.obtain(r.finishing, r.configChangeFlags)); - } catch (Exception e) { - // We can just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - //Slog.w(TAG, "Exception thrown during finish", e); - if (r.finishing) { - removeActivityFromHistoryLocked(r, reason + " exceptionInScheduleDestroy"); - removedFromHistory = true; - skipDestroy = true; - } - } - - r.nowVisible = false; - - // If the activity is finishing, we need to wait on removing it - // from the list to give it a chance to do its cleanup. During - // that time it may make calls back with its token so we need to - // be able to find it on the list and so we don't want to remove - // it from the list yet. Otherwise, we can just immediately put - // it in the destroyed state since we are not removing it from the - // list. - if (r.finishing && !skipDestroy) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYING: " + r - + " (destroy requested)"); - r.setState(DESTROYING, - "destroyActivityLocked. finishing and not skipping destroy"); - Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r); - mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); - } else { - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Moving to DESTROYED: " + r + " (destroy skipped)"); - r.setState(DESTROYED, - "destroyActivityLocked. not finishing or skipping destroy"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r); - r.app = null; - } - } else { - // remove this record from the history. - if (r.finishing) { - removeActivityFromHistoryLocked(r, reason + " hadNoApp"); - removedFromHistory = true; - } else { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (no app)"); - r.setState(DESTROYED, "destroyActivityLocked. not finishing and had no app"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r); - r.app = null; - } - } - - r.configChangeFlags = 0; - - if (!mLRUActivities.remove(r) && hadApp) { - Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); - } - - return removedFromHistory; - } - - final void activityDestroyedLocked(IBinder token, String reason) { - final long origId = Binder.clearCallingIdentity(); - try { - activityDestroyedLocked(ActivityRecord.forTokenLocked(token), reason); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - /** - * This method is to only be called from the client via binder when the activity is destroyed - * AND finished. - */ - final void activityDestroyedLocked(ActivityRecord record, String reason) { - if (record != null) { - mHandler.removeMessages(DESTROY_TIMEOUT_MSG, record); - } - - if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + record); - - if (isInStackLocked(record) != null) { - if (record.isState(DESTROYING, DESTROYED)) { - cleanUpActivityLocked(record, true, false); - removeActivityFromHistoryLocked(record, reason); - } - } - - mRootActivityContainer.resumeFocusedStacksTopActivities(); - } - private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list, WindowProcessController app, String listName) { int i = list.size(); @@ -4459,7 +4065,7 @@ class ActivityStack extends ConfigurationContainer { if (r.app == app) { if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "---> REMOVING this entry!"); list.remove(i); - removeTimeoutsForActivityLocked(r); + removeTimeoutsForActivity(r); } } } @@ -4554,9 +4160,9 @@ class ActivityStack extends ConfigurationContainer { // other apps when user transfers focus to the restarted activity. r.nowVisible = r.visible; } - cleanUpActivityLocked(r, true, true); + r.cleanUp(true /* cleanServices */, true /* setState */); if (remove) { - removeActivityFromHistoryLocked(r, "appDied"); + r.removeFromHistory("appDied"); } } } @@ -4747,16 +4353,6 @@ class ActivityStack extends ConfigurationContainer { return true; } - static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { - final Uri data = r.intent.getData(); - final String strData = data != null ? data.toSafeString() : null; - - EventLog.writeEvent(tag, - r.mUserId, System.identityHashCode(r), task.taskId, - r.shortComponentName, r.intent.getAction(), - r.intent.getType(), strData, r.intent.getFlags()); - } - /** * Ensures all visible activities at or below the input activity have the right configuration. */ @@ -4890,7 +4486,7 @@ class ActivityStack extends ConfigurationContainer { for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { - r.finishActivityLocked(Activity.RESULT_CANCELED, null, "close-sys", true); + r.finishIfPossible("close-sys", true /* oomAdj */); } } } @@ -4934,7 +4530,7 @@ class ActivityStack extends ConfigurationContainer { didSomething = true; Slog.i(TAG, " Force finishing activity " + r); lastTask = r.getTaskRecord(); - r.finishActivityLocked(Activity.RESULT_CANCELED, null, "force-stop", true); + r.finishIfPossible("force-stop", true); } } } @@ -4988,8 +4584,7 @@ class ActivityStack extends ConfigurationContainer { final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities; int activityTop = activities.size() - 1; if (activityTop >= 0) { - activities.get(activityTop).finishActivityLocked(Activity.RESULT_CANCELED, null, - "unhandled-back", true); + activities.get(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */); } } } @@ -5025,8 +4620,7 @@ class ActivityStack extends ConfigurationContainer { r.app = null; getDisplay().mDisplayContent.prepareAppTransition( TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); - r.finishCurrentActivityLocked(FINISH_IMMEDIATELY, false /* oomAdj */, - "handleAppCrashedLocked"); + r.destroyIfPossible("handleAppCrashedLocked"); } } } @@ -5167,7 +4761,7 @@ class ActivityStack extends ConfigurationContainer { EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId()); } - removeActivitiesFromLRUListLocked(task); + removeActivitiesFromLRUList(task); updateTaskMovement(task, true); if (mode == REMOVE_TASK_MODE_DESTROYING) { @@ -5350,7 +4944,7 @@ class ActivityStack extends ConfigurationContainer { // If the activity was previously pausing, then ensure we transfer that as well if (setPause) { mPausingActivity = r; - schedulePauseTimeout(r); + schedulePauseTimeoutForActivity(r); } // Move the stack in which we are placing the activity to the front. moveToFront(reason); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 22f72a499aff..a06b9ce5d645 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.graphics.Rect.copyOrNull; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; @@ -897,8 +898,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { Slog.e(TAG, "Second failure launching " + r.intent.getComponent().flattenToShortString() + ", giving up", e); proc.appDied(); - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "2nd-crash", false /* oomAdj */); + r.finishIfPossible("2nd-crash", false /* oomAdj */); return false; } @@ -1029,9 +1029,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) { if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, + Activity.RESULT_CANCELED, null /* data */); } final String msg; if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) { @@ -1347,10 +1346,10 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ActivityStack stack = r.getActivityStack(); if (stack != null) { if (r.finishing) { - r.finishCurrentActivityLocked(ActivityRecord.FINISH_IMMEDIATELY, - false /* oomAdj */, "activityIdleInternalLocked"); + // TODO(b/137329632): Wait for idle of the right activity, not just any. + r.destroyIfPossible("activityIdleInternalLocked"); } else { - stack.stopActivityLocked(r); + r.stopIfPossible(); } } } @@ -1361,7 +1360,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { r = finishes.get(i); final ActivityStack stack = r.getActivityStack(); if (stack != null) { - activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle"); + activityRemoved |= r.destroyImmediately(true /* removeFromApp */, "finish-idle"); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 9ed93c46733a..48bc96346e9e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -55,6 +55,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Process.INVALID_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -752,8 +753,8 @@ class ActivityStarter { if (err != START_SUCCESS) { if (resultRecord != null) { - resultStack.sendActivityResultLocked( - -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } SafeActivityOptions.abort(options); return err; @@ -817,8 +818,8 @@ class ActivityStarter { if (abort) { if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } // We pretend to the caller that it was really started, but // they will just get a cancel result. @@ -1426,8 +1427,7 @@ class ActivityStarter { // performing operations without a window container. final ActivityStack stack = mStartActivity.getActivityStack(); if (stack != null) { - mStartActivity.finishActivityLocked(RESULT_CANCELED, - null /* intentResultData */, "startActivity", true /* oomAdj */); + mStartActivity.finishIfPossible("startActivity", true /* oomAdj */); } // Stack should also be detached from display and be removed if it's empty. @@ -1451,18 +1451,17 @@ class ActivityStarter { * TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere. */ private boolean handleBackgroundActivityAbort(ActivityRecord r) { - // TODO(b/131747138): Remove toast and refactor related code in Q release. - boolean abort = !mService.isBackgroundActivityStartsEnabled(); + // TODO(b/131747138): Remove toast and refactor related code in R release. + final boolean abort = !mService.isBackgroundActivityStartsEnabled(); if (!abort) { return false; } - ActivityRecord resultRecord = r.resultTo; - String resultWho = r.resultWho; + final ActivityRecord resultRecord = r.resultTo; + final String resultWho = r.resultWho; int requestCode = r.requestCode; if (resultRecord != null) { - ActivityStack resultStack = resultRecord.getActivityStack(); - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } // We pretend to the caller that it was really started to make it backward compatible, but // they will just get a cancel result. @@ -1627,12 +1626,9 @@ class ActivityStarter { } if (mStartActivity.packageName == null) { - final ActivityStack sourceStack = mStartActivity.resultTo != null - ? mStartActivity.resultTo.getActivityStack() : null; - if (sourceStack != null) { - sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo, - mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, - null /* data */); + if (mStartActivity.resultTo != null) { + mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho, + mStartActivity.requestCode, RESULT_CANCELED, null /* data */); } ActivityOptions.abort(mOptions); return START_CLASS_NOT_FOUND; @@ -1709,8 +1705,8 @@ class ActivityStarter { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId, mStartActivity.getTaskRecord().taskId); } - ActivityStack.logStartActivity( - EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTaskRecord()); + mStartActivity.logStartActivity( + EventLogTags.AM_CREATE_ACTIVITY, mStartActivity.getTaskRecord()); mTargetStack.mLastPausedActivity = null; mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded( @@ -1928,17 +1924,14 @@ class ActivityStarter { } private void sendNewTaskResultRequestIfNeeded() { - final ActivityStack sourceStack = mStartActivity.resultTo != null - ? mStartActivity.resultTo.getActivityStack() : null; - if (sourceStack != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { // For whatever reason this activity is being launched into a new task... // yet the caller has requested a result back. Well, that is pretty messed up, // so instead immediately send back a cancel and let the new task continue launched // as normal without a dependency on its originator. Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo, - mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, - null /* data */); + mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho, + mStartActivity.requestCode, RESULT_CANCELED, null /* data */); mStartActivity.resultTo = null; } } @@ -2363,7 +2356,7 @@ class ActivityStarter { return; } - ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTaskRecord()); + activity.logStartActivity(AM_NEW_INTENT, activity.getTaskRecord()); activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage); mIntentDelivered = true; @@ -2430,7 +2423,7 @@ class ActivityStarter { ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags); mKeepCurTransition = true; if (top != null) { - ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTaskRecord()); + mStartActivity.logStartActivity(AM_NEW_INTENT, top.getTaskRecord()); deliverNewIntent(top); // For paranoia, make sure we have correctly resumed the top activity. mTargetStack.mLastPausedActivity = null; @@ -2449,7 +2442,7 @@ class ActivityStarter { final TaskRecord task = top.getTaskRecord(); task.moveActivityToFrontLocked(top); top.updateOptionsLocked(mOptions); - ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task); + mStartActivity.logStartActivity(AM_NEW_INTENT, task); deliverNewIntent(top); mTargetStack.mLastPausedActivity = null; if (mDoResume) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a7b6e0ff0630..3c5947a51e11 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -46,7 +46,6 @@ import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEME import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; -import static android.os.Build.VERSION_CODES.N; import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_OFF; @@ -86,6 +85,7 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; +import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; @@ -1614,8 +1614,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Explicitly dismissing the activity so reset its relaunch flag. r.mRelaunchReason = RELAUNCH_REASON_NONE; } else { - r.finishActivityLocked(resultCode, resultData, "app-request", - true /* oomAdj */); + r.finishIfPossible(resultCode, resultData, "app-request", + true /* oomAdj */, !PAUSE_IMMEDIATELY); res = r.finishing; if (!res) { Slog.i(TAG, "Failed to finish by app-request"); @@ -1753,9 +1753,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public final void activityDestroyed(IBinder token) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token); synchronized (mGlobalLock) { - ActivityStack stack = ActivityRecord.getStackLocked(token); - if (stack != null) { - stack.activityDestroyedLocked(token, "activityDestroyed"); + final long origId = Binder.clearCallingIdentity(); + try { + final ActivityRecord activity = ActivityRecord.forTokenLocked(token); + if (activity != null) { + activity.destroyed("activityDestroyed"); + } + } finally { + Binder.restoreCallingIdentity(origId); } } } @@ -3236,7 +3241,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (r == null) { return false; } - return r.getActivityStack().safelyDestroyActivityLocked(r, "app-req"); + return r.safelyDestroy("app-req"); } finally { Binder.restoreCallingIdentity(origId); } @@ -6484,8 +6489,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken); if (r != null && r.getActivityStack() != null) { - r.getActivityStack().sendActivityResultLocked(callingUid, r, resultWho, - requestCode, resultCode, data); + r.sendResult(callingUid, resultWho, requestCode, resultCode, data); } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4135ef3f2e0b..e851a06cbb85 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -796,10 +796,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTmpApplySurfaceChangesTransactionState.obscured = true; } - mTmpApplySurfaceChangesTransactionState.displayHasContent |= - root.handleNotObscuredLocked(w, - mTmpApplySurfaceChangesTransactionState.obscured, - mTmpApplySurfaceChangesTransactionState.syswin); + final boolean displayHasContent = root.handleNotObscuredLocked(w, + mTmpApplySurfaceChangesTransactionState.obscured, + mTmpApplySurfaceChangesTransactionState.syswin); + + if (!mTmpApplySurfaceChangesTransactionState.displayHasContent + && !getDisplayPolicy().isWindowExcludedFromContent(w)) { + mTmpApplySurfaceChangesTransactionState.displayHasContent |= displayHasContent; + } if (w.mHasSurface && isDisplayed) { final int type = w.mAttrs.type; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e35ef258e96d..e5962ae19413 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3790,6 +3790,20 @@ public class DisplayPolicy { mPointerLocationView = null; } + /** + * Check if the window could be excluded from checking if the display has content. + * + * @param w WindowState to check if should be excluded. + * @return True if the window type is PointerLocation which is excluded. + */ + boolean isWindowExcludedFromContent(WindowState w) { + if (w != null && mPointerLocationView != null) { + return w.mClient == mPointerLocationView.getWindowToken(); + } + + return false; + } + @VisibleForTesting static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) { if (navBarWindow == null || !navBarWindow.isVisibleLw() diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index caa836376248..1a8944ab415b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -149,8 +149,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more // things (e.g. the measure can be done earlier). The actual stop will be performed when // it reports idle. - targetStack.addToStopping(targetActivity, true /* scheduleIdle */, - true /* idleDelayed */, "preloadRecents"); + targetActivity.addToStopping(true /* scheduleIdle */, true /* idleDelayed */, + "preloadRecents"); } } diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index f51d4c4aa3f7..1c015d0787dd 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -159,11 +159,23 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // STEP 2: Resolve launch windowing mode. - // STEP 2.1: Determine if any parameter has specified initial bounds. That might be the - // launch bounds from activity options, or size/gravity passed in layout. It also treats the - // launch windowing mode in options as a suggestion for future resolution. + // STEP 2.1: Determine if any parameter can specify initial bounds/windowing mode. That + // might be the launch bounds from activity options, or size/gravity passed in layout. It + // also treats the launch windowing mode in options and source activity windowing mode in + // some cases as a suggestion for future resolution. int launchMode = options != null ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; + // In some cases we want to use the source's windowing mode as the default value, e.g. when + // source is a freeform window in a fullscreen display launching an activity on the same + // display. + if (launchMode == WINDOWING_MODE_UNDEFINED + && canInheritWindowingModeFromSource(display, source)) { + launchMode = source.getWindowingMode(); + if (DEBUG) { + appendLog("inherit-from-source=" + + WindowConfiguration.windowingModeToString(launchMode)); + } + } // hasInitialBounds is set if either activity options or layout has specified bounds. If // that's set we'll skip some adjustments later to avoid overriding the initial bounds. boolean hasInitialBounds = false; @@ -342,6 +354,31 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { ? displayId : DEFAULT_DISPLAY; } + private boolean canInheritWindowingModeFromSource(@NonNull ActivityDisplay display, + @Nullable ActivityRecord source) { + if (source == null) { + return false; + } + + // There is not really any strong reason to tie the launching windowing mode and the source + // on freeform displays. The launching windowing mode is more tied to the content of the new + // activities. + if (display.inFreeformWindowingMode()) { + return false; + } + + final int sourceWindowingMode = source.getWindowingMode(); + if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN + && sourceWindowingMode != WINDOWING_MODE_FREEFORM) { + return false; + } + + // Only inherit windowing mode if both source and target activities are on the same display. + // Otherwise we may have unintended freeform windows showing up if an activity in freeform + // window launches an activity on a fullscreen display by specifying display ID. + return display.mDisplayId == source.getDisplayId(); + } + private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display, int launchMode) { return mSupervisor.mService.mSupportsFreeformWindowManagement && (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM); diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 882f41152886..ede2f568e69e 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -1419,8 +1419,9 @@ class TaskRecord extends ConfigurationContainer { mActivities.remove(activityNdx); --activityNdx; --numActivities; - } else if (r.finishActivityLocked(Activity.RESULT_CANCELED, null, - reason, false /* oomAdj */, pauseImmediately) == FINISH_RESULT_REMOVED) { + } else if (r.finishIfPossible(Activity.RESULT_CANCELED, + null /* resultData */, reason, false /* oomAdj */, pauseImmediately) + == FINISH_RESULT_REMOVED) { --activityNdx; --numActivities; } @@ -1474,8 +1475,8 @@ class TaskRecord extends ConfigurationContainer { if (opts != null) { ret.updateOptionsLocked(opts); } - if (r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "clear-task-stack", false /* oomAdj */) == FINISH_RESULT_REMOVED) { + if (r.finishIfPossible("clear-task-stack", false /* oomAdj */) + == FINISH_RESULT_REMOVED) { --activityNdx; --numActivities; } @@ -1488,8 +1489,7 @@ class TaskRecord extends ConfigurationContainer { && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0 && !ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) { if (!ret.finishing) { - ret.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "clear-task-top", false /* oomAdj */); + ret.finishIfPossible("clear-task-top", false /* oomAdj */); return null; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 05cfbd4e39d9..750926f11180 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -52,9 +52,12 @@ public abstract class WindowManagerInternal { * Called when the windows for accessibility changed. * * @param forceSend Send the windows for accessibility even if they haven't changed. + * @param topFocusedDisplayId The display Id which has the top focused window. + * @param topFocusedWindowToken The window token of top focused window. * @param windows The windows for accessibility. */ - void onWindowsForAccessibilityChanged(boolean forceSend, List<WindowInfo> windows); + void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId, + IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d8d6841fe942..3874fdcec6cd 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7717,8 +7717,7 @@ public class WindowManagerService extends IWindowManager.Stub clientChannel.transferTo(outInputChannel); clientChannel.dispose(); - IBinder token = new Binder(); - mInputManager.registerInputChannel(inputChannel, token); + mInputManager.registerInputChannel(inputChannel, null /* generate new token */); // Prevent the java finalizer from breaking the input channel. But we won't // do any further management so we just release the java ref and let the @@ -7726,7 +7725,7 @@ public class WindowManagerService extends IWindowManager.Stub inputChannel.release(); InputWindowHandle h = new InputWindowHandle(null, null, displayId); - h.token = token; + h.token = inputChannel.getToken(); h.name = name; h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; h.layoutParamsType = 0; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index cf8e1e80ccc3..242b116b0315 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -41,7 +41,6 @@ import static com.android.server.wm.ActivityTaskManagerService.KEY_DISPATCHING_T import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import android.annotation.NonNull; -import android.app.Activity; import android.app.ActivityThread; import android.app.IApplicationThread; import android.app.ProfilerInfo; @@ -644,8 +643,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio for (int i = 0; i < activities.size(); i++) { final ActivityRecord r = activities.get(i); if (!r.finishing && r.isInStackLocked()) { - r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, - "finish-heavy", true /* oomAdj */); + r.finishIfPossible("finish-heavy", true /* oomAdj */); } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java index 3614763fecab..5d041b7c5757 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java @@ -71,6 +71,7 @@ public class TimeControllerTest { private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; + private TimeController.TcConstants mConstants; private TimeController mTimeController; private MockitoSession mMockingSession; @@ -110,6 +111,7 @@ public class TimeControllerTest { // Initialize real objects. mTimeController = new TimeController(mJobSchedulerService); + mConstants = mTimeController.getTcConstants(); spyOn(mTimeController); } @@ -528,6 +530,46 @@ public class TimeControllerTest { } @Test + public void testJobDelayWakeupAlarmToggling() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus job = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(job), anyInt()); + + // Starting off with using a wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false; + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), + eq(TAG_DELAY), any(), any(), any()); + + // Use a non wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = true; + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + // Back off, use a wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false; + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), + eq(TAG_DELAY), any(), any(), any()); + } + + @Test public void testCheckExpiredDeadlinesAndResetAlarm_AllReady() { doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 1084d625f8a3..356423ad0590 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -76,6 +76,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.testing.DexmakerShareClassLoaderRule; +import android.view.Display; import android.view.KeyEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; @@ -178,7 +179,8 @@ public class AbstractAccessibilityServiceConnectionTest { // Fake a11yWindowInfo and remote a11y connection for tests. addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false); addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true); - when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(mA11yWindowInfos); + when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)) + .thenReturn(mA11yWindowInfos); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID)) .thenReturn(mA11yWindowInfos.get(0)); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID)) @@ -289,8 +291,9 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getWindows_notTrackingWindows_invokeOnClientChange() { - when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(null); - when(mMockA11yWindowManager.isTrackingWindowsLocked()).thenReturn(false); + when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)).thenReturn(null); + when(mMockA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) + .thenReturn(false); mServiceConnection.getWindows(); verify(mMockSystemSupport).onClientChangeLocked(false); @@ -315,8 +318,9 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getWindow_notTrackingWindows_invokeOnClientChange() { - when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(null); - when(mMockA11yWindowManager.isTrackingWindowsLocked()).thenReturn(false); + when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)).thenReturn(null); + when(mMockA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) + .thenReturn(false); mServiceConnection.getWindow(WINDOWID); verify(mMockSystemSupport).onClientChangeLocked(false); @@ -752,7 +756,7 @@ public class AbstractAccessibilityServiceConnectionTest { } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { return mResolvedUserId == mSystemSupport.getCurrentUserIdLocked(); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 93c16fedcc1b..b7b5a4eaacfa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -79,17 +79,22 @@ public class AccessibilityWindowManagerTest { private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM; private static final int USER_PROFILE = 11; private static final int USER_PROFILE_PARENT = 1; - // TO-DO [Multi-Display] : change the display count to 2 - private static final int DISPLAY_COUNT = 1; + private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; private static final int NUM_GLOBAL_WINDOWS = 4; private static final int NUM_APP_WINDOWS = 4; - private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS) - * DISPLAY_COUNT; + private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS); private static final int DEFAULT_FOCUSED_INDEX = 1; private static final int SCREEN_WIDTH = 1080; private static final int SCREEN_HEIGHT = 1920; private AccessibilityWindowManager mA11yWindowManager; + // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true, + // i.e., each display would have its current focused window, and one of all focused windows + // would be top focused window. Otherwise, window manager only supports one focused window + // at all displays, and that focused window would be top focused window. + private boolean mSupportPerDisplayFocus = false; + private int mTopFocusedDisplayId = Display.INVALID_DISPLAY; + private IBinder mTopFocusedWindowToken = null; // List of window token, mapping from windowId -> window token. private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>(); @@ -123,12 +128,9 @@ public class AccessibilityWindowManagerTest { mMockA11yEventSender, mMockA11ySecurityPolicy, mMockA11yUserManager); - - for (int i = 0; i < DISPLAY_COUNT; i++) { - when(mMockWindowManagerInternal.setWindowsForAccessibilityCallback(eq(i), any())) - .thenReturn(true); - startTrackingPerDisplay(i); - } + // Starts tracking window of default display and sets the default display + // as top focused display before each testing starts. + startTrackingPerDisplay(Display.DEFAULT_DISPLAY); // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged. // Resets it for mockito verify of further test case. @@ -143,7 +145,7 @@ public class AccessibilityWindowManagerTest { @Test public void startTrackingWindows_shouldEnableWindowManagerCallback() { // AccessibilityWindowManager#startTrackingWindows already invoked in setup. - assertTrue(mA11yWindowManager.isTrackingWindowsLocked()); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); final WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(Display.DEFAULT_DISPLAY); verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback( @@ -152,11 +154,11 @@ public class AccessibilityWindowManagerTest { @Test public void stopTrackingWindows_shouldDisableWindowManagerCallback() { - assertTrue(mA11yWindowManager.isTrackingWindowsLocked()); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); Mockito.reset(mMockWindowManagerInternal); - mA11yWindowManager.stopTrackingWindows(); - assertFalse(mA11yWindowManager.isTrackingWindowsLocked()); + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback( eq(Display.DEFAULT_DISPLAY), isNull()); @@ -164,11 +166,11 @@ public class AccessibilityWindowManagerTest { @Test public void stopTrackingWindows_shouldClearWindows() { - assertTrue(mA11yWindowManager.isTrackingWindowsLocked()); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); - mA11yWindowManager.stopTrackingWindows(); - assertNull(mA11yWindowManager.getWindowListLocked()); + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)); assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), @@ -176,6 +178,20 @@ public class AccessibilityWindowManagerTest { } @Test + public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID)); + // Stops tracking windows of second display. + mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID); + assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), + AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); + } + + @Test public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() { final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); WindowInfo focusedWindowInfo = @@ -196,10 +212,66 @@ public class AccessibilityWindowManagerTest { } @Test + public void + onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true. + mSupportPerDisplayFocus = true; + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + // Gets the active window. + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + // Gets the top focused window. + final int topFocusedWindowId = + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT); + // Changes the current focused window at second display. + changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID, + DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX); + + onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES); + // The active window should not be changed. + assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID)); + // The top focused window should not be changed. + assertEquals(topFocusedWindowId, + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT)); + } + + @Test + public void + onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is + // false. + mSupportPerDisplayFocus = false; + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + // Gets the active window. + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + // Gets the top focused window. + final int topFocusedWindowId = + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT); + // Changes the current focused window from default display to second display. + changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID, + DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES); + // The active window should be changed. + assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID)); + // The top focused window should be changed. + assertNotEquals(topFocusedWindowId, + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT)); + } + + @Test public void onWindowsChanged_shouldReportCorrectLayer() { // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup. List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); for (int i = 0; i < a11yWindows.size(); i++) { final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i); final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i); @@ -212,7 +284,7 @@ public class AccessibilityWindowManagerTest { public void onWindowsChanged_shouldReportCorrectOrder() { // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup. List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); for (int i = 0; i < a11yWindows.size(); i++) { final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i); final IBinder windowToken = mA11yWindowManager @@ -226,31 +298,31 @@ public class AccessibilityWindowManagerTest { public void onWindowsChangedAndForceSend_shouldUpdateWindows() { final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); final int correctLayer = - mA11yWindowManager.getWindowListLocked().get(0).getLayer(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer(); windowInfo.layer += 1; onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND); assertNotEquals(correctLayer, - mA11yWindowManager.getWindowListLocked().get(0).getLayer()); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer()); } @Test public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() { final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); final int correctLayer = - mA11yWindowManager.getWindowListLocked().get(0).getLayer(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer(); windowInfo.layer += 1; onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); assertEquals(correctLayer, - mA11yWindowManager.getWindowListLocked().get(0).getLayer()); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer()); } @Test public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows() throws RemoteException { final AccessibilityWindowInfo oldWindow = - mA11yWindowManager.getWindowListLocked().get(0); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0); final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, true, USER_SYSTEM_ID); final WindowInfo windowInfo = WindowInfo.obtain(); @@ -262,7 +334,7 @@ public class AccessibilityWindowManagerTest { onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); assertNotEquals(oldWindow, - mA11yWindowManager.getWindowListLocked().get(0)); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)); } @Test @@ -274,7 +346,8 @@ public class AccessibilityWindowManagerTest { windowInfo.focused = true; onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); - assertTrue(mA11yWindowManager.getWindowListLocked().get(0).isFocused()); + assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0) + .isFocused()); } @Test @@ -305,7 +378,7 @@ public class AccessibilityWindowManagerTest { @Test public void getWindowTokenForUserAndWindowId_shouldNotNull() { final List<AccessibilityWindowInfo> windows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); for (int i = 0; i < windows.size(); i++) { final int windowId = windows.get(i).getId(); @@ -317,7 +390,7 @@ public class AccessibilityWindowManagerTest { @Test public void findWindowId() { final List<AccessibilityWindowInfo> windows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); for (int i = 0; i < windows.size(); i++) { final int windowId = windows.get(i).getId(); final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( @@ -339,7 +412,7 @@ public class AccessibilityWindowManagerTest { onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); final List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); final Region outBounds = new Region(); int windowId = a11yWindows.get(0).getId(); @@ -362,7 +435,7 @@ public class AccessibilityWindowManagerTest { onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); final List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); final Region outBounds = new Region(); int windowId = a11yWindows.get(1).getId(); @@ -375,7 +448,7 @@ public class AccessibilityWindowManagerTest { public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() { // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible. final List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); final Region outBounds = new Region(); int windowId = a11yWindows.get(1).getId(); @@ -393,7 +466,7 @@ public class AccessibilityWindowManagerTest { onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); final List<AccessibilityWindowInfo> a11yWindows = - mA11yWindowManager.getWindowListLocked(); + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); final Region outBounds = new Region(); int windowId = a11yWindows.get(1).getId(); @@ -413,7 +486,7 @@ public class AccessibilityWindowManagerTest { .thenReturn(eventWindowToken); final int noUse = 0; - mA11yWindowManager.stopTrackingWindows(); + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, eventWindowId, noUse, @@ -553,7 +626,7 @@ public class AccessibilityWindowManagerTest { mA11yWindowManager.getConnectionLocked( USER_SYSTEM_ID, newFocusWindowId).getRemote(); - mA11yWindowManager.stopTrackingWindows(); + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); final int noUse = 0; mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, defaultFocusWindowId, @@ -636,20 +709,27 @@ public class AccessibilityWindowManagerTest { false, USER_SYSTEM_ID); addWindowInfo(windowInfosForDisplay, token, layer++); } - // Setups default focus. - windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true; + // Sets up current focused window of display. + // Each display has its own current focused window if config_perDisplayFocusEnabled is true. + // Otherwise only default display needs to current focused window. + if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) { + windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true; + } // Turns on windows tracking, and update window info. - mA11yWindowManager.startTrackingWindows(); + when(mMockWindowManagerInternal.setWindowsForAccessibilityCallback(eq(displayId), any())) + .thenReturn(true); + mA11yWindowManager.startTrackingWindows(displayId); // Puts window lists into array. mWindowInfos.put(displayId, windowInfosForDisplay); - // Sets the default display as the top focused display. + // Sets the default display is the top focused display and + // its current focused window is the top focused window. if (displayId == Display.DEFAULT_DISPLAY) { setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX); } // Invokes callback for sending window lists to A11y framework. onWindowsForAccessibilityChanged(displayId, FORCE_SEND); - assertEquals(mA11yWindowManager.getWindowListLocked().size(), + assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(), windowInfosForDisplay.size()); } @@ -700,12 +780,9 @@ public class AccessibilityWindowManagerTest { private void setTopFocusedWindowAndDisplay(int displayId, int index) { // Sets the top focus window. - final IBinder eventWindowToken = mWindowInfos.get(displayId).get(index).token; - when(mMockWindowManagerInternal.getFocusedWindowToken()) - .thenReturn(eventWindowToken); + mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token; // Sets the top focused display. - when(mMockWindowManagerInternal.getDisplayIdForWindow(eventWindowToken)) - .thenReturn(displayId); + mTopFocusedDisplayId = displayId; } private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) { @@ -714,7 +791,38 @@ public class AccessibilityWindowManagerTest { callbacks = getWindowsForAccessibilityCallbacks(displayId); mCallbackOfWindows.put(displayId, callbacks); } - callbacks.onWindowsForAccessibilityChanged(forceSend, mWindowInfos.get(displayId)); + callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId, + mTopFocusedWindowToken, mWindowInfos.get(displayId)); + } + + private void changeFocusedWindowOnDisplayPerDisplayFocusConfig( + int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId, + int oldFocusedWindowIndex) { + if (mSupportPerDisplayFocus) { + // Gets the old focused window of display which wants to change focused window. + WindowInfo focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex); + // Resets the focus of old focused window. + focusedWindowInfo.focused = false; + // Gets the new window of display which wants to change focused window. + focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex); + // Sets the focus of new focused window. + focusedWindowInfo.focused = true; + } else { + // Gets the window of display which wants to change focused window. + WindowInfo focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex); + // Sets the focus of new focused window. + focusedWindowInfo.focused = true; + // Gets the old focused window of old top focused display. + focusedWindowInfo = + mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex); + // Resets the focus of old focused window. + focusedWindowInfo.focused = false; + // Changes the top focused display and window. + setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex); + } } static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java index 65c5781a9114..41142f6b8505 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java @@ -57,15 +57,15 @@ public class AppOpsActiveWatcherTest { // Start watching active ops final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); - appOpsManager.startWatchingActive(new int[] {AppOpsManager.OP_CAMERA, - AppOpsManager.OP_RECORD_AUDIO}, listener); + appOpsManager.startWatchingActive(new String[] {AppOpsManager.OPSTR_CAMERA, + AppOpsManager.OPSTR_RECORD_AUDIO}, getContext().getMainExecutor(), listener); // Start the op appOpsManager.startOp(AppOpsManager.OP_CAMERA); // Verify that we got called for the op being active verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) - .times(1)).onOpActiveChanged(eq(AppOpsManager.OP_CAMERA), + .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA), eq(Process.myUid()), eq(getContext().getPackageName()), eq(true)); // This should be the only callback we got @@ -83,7 +83,7 @@ public class AppOpsActiveWatcherTest { // Verify that we got called for the op being active verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) - .times(1)).onOpActiveChanged(eq(AppOpsManager.OP_CAMERA), + .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA), eq(Process.myUid()), eq(getContext().getPackageName()), eq(false)); // Verify that the op is not active @@ -155,4 +155,4 @@ public class AppOpsActiveWatcherTest { private static Context getContext() { return InstrumentationRegistry.getContext(); } -}
\ No newline at end of file +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 57caa1db423c..3ac7a79a1630 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -181,12 +181,6 @@ import java.util.function.Consumer; @RunWithLooper public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; - private static final String CLEAR_DEVICE_CONFIG_KEY_CMD = - "device_config delete " + DeviceConfig.NAMESPACE_SYSTEMUI + " " - + SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE; - private static final String SET_DEFAULT_ASSISTANT_DEVICE_CONFIG_CMD = - "device_config put " + DeviceConfig.NAMESPACE_SYSTEMUI + " " - + SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE; private final int mUid = Binder.getCallingUid(); private TestableNotificationManagerService mService; @@ -4040,6 +4034,41 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testPostFromAndroidForNonExistentPackage() throws Exception { + final String notReal = "NOT REAL"; + when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())).thenThrow( + PackageManager.NameNotFoundException.class); + ApplicationInfo ai = new ApplicationInfo(); + ai.uid = -1; + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); + + final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + try { + mInternalService.enqueueNotification(notReal, "android", 0, 0, "tag", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + fail("can't post notifications for nonexistent packages, even if you exist"); + } catch (SecurityException e) { + // yay + } + } + + @Test + public void testCancelFromAndroidForNonExistentPackage() throws Exception { + final String notReal = "NOT REAL"; + when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow( + PackageManager.NameNotFoundException.class); + ApplicationInfo ai = new ApplicationInfo(); + ai.uid = -1; + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); + + // unlike the post case, ignore instead of throwing + final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + + mInternalService.cancelNotification(notReal, "android", 0, 0, "tag", + sbn.getId(), sbn.getUserId()); + } + + @Test public void testResolveNotificationUid_delegateNotAllowed() throws Exception { when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(123); // no delegate diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 8f8b746b59d4..365cd80c88c7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -22,6 +22,8 @@ import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; + import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; @@ -2690,4 +2692,51 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O)); verify(mHandler, times(1)).requestSort(); } + + @Test + public void testTooManyChannels() { + for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) { + NotificationChannel channel = new NotificationChannel(String.valueOf(i), + String.valueOf(i), NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true); + } + try { + NotificationChannel channel = new NotificationChannel( + String.valueOf(NOTIFICATION_CHANNEL_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_COUNT_LIMIT), + NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true); + fail("Allowed to create too many notification channels"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyChannels_xml() throws Exception { + String extraChannel = "EXTRA"; + String extraChannel1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) { + NotificationChannel channel = new NotificationChannel(String.valueOf(i), + String.valueOf(i), NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true); + } + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + + "<channel id=\"" + extraChannel + "\" name=\"hi\" importance=\"3\"/>" + + "<channel id=\"" + extraChannel1 + "\" name=\"hi\" importance=\"3\"/>" + + "</package>" + + "</ranking>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel, true)); + assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true)); + } } 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 0f047881d7df..13748cbf8c9c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -26,6 +26,7 @@ import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; @@ -34,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -41,12 +43,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; +import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; +import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STARTED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; +import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; @@ -61,6 +68,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -87,6 +95,7 @@ import android.view.RemoteAnimationTarget; import androidx.test.filters.MediumTest; import com.android.internal.R; +import com.android.server.wm.ActivityStack.ActivityState; import org.junit.Before; import org.junit.Test; @@ -544,12 +553,16 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityDisplay newDisplay = addNewActivityDisplayAt(info, ActivityDisplay.POSITION_TOP); - mTask.getConfiguration().densityDpi = 200; + final Configuration c = + new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration()); + c.densityDpi = 200; + mStack.getDisplay().onRequestedOverrideConfigurationChanged(c); mActivity = new ActivityBuilder(mService) .setTask(mTask) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setMaxAspectRatio(1.5f) .build(); + mActivity.visible = true; final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; @@ -586,7 +599,7 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testSizeCompatMode_FixedScreenLayoutSizeBits() { final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO | Configuration.SCREENLAYOUT_SIZE_NORMAL; - mTask.getConfiguration().screenLayout = fixedScreenLayout + mTask.getRequestedOverrideConfiguration().screenLayout = fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR; prepareFixedAspectRatioUnresizableActivity(); @@ -726,12 +739,11 @@ public class ActivityRecordTests extends ActivityTestsBase { * incorrect state. */ @Test - public void testFinishActivityLocked_cancelled() { + public void testFinishActivityIfPossible_cancelled() { // Mark activity as finishing mActivity.finishing = true; assertEquals("Duplicate finish request must be ignored", FINISH_RESULT_CANCELLED, - mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test", - false /* oomAdj */)); + mActivity.finishIfPossible("test", false /* oomAdj */)); assertTrue(mActivity.finishing); assertTrue(mActivity.isInStackLocked()); @@ -739,21 +751,19 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.finishing = false; mActivity.setTask(null); assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_CANCELLED, - mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test", - false /* oomAdj */)); + mActivity.finishIfPossible("test", false /* oomAdj */)); assertFalse(mActivity.finishing); } /** - * Verify that activity finish request is requested, but not executed immediately if activity is + * Verify that activity finish request is placed, but not executed immediately if activity is * not ready yet. */ @Test - public void testFinishActivityLocked_requested() { + public void testFinishActivityIfPossible_requested() { mActivity.finishing = false; - assertEquals("Currently resumed activity be paused removal", FINISH_RESULT_REQUESTED, - mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test", - false /* oomAdj */)); + assertEquals("Currently resumed activity must be prepared removal", FINISH_RESULT_REQUESTED, + mActivity.finishIfPossible("test", false /* oomAdj */)); assertTrue(mActivity.finishing); assertTrue(mActivity.isInStackLocked()); @@ -762,8 +772,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.finishing = false; mActivity.setState(STOPPED, "test"); assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REQUESTED, - mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test", - false /* oomAdj */)); + mActivity.finishIfPossible("test", false /* oomAdj */)); assertTrue(mActivity.finishing); assertTrue(mActivity.isInStackLocked()); } @@ -772,7 +781,7 @@ public class ActivityRecordTests extends ActivityTestsBase { * Verify that activity finish request removes activity immediately if it's ready. */ @Test - public void testFinishActivityLocked_removed() { + public void testFinishActivityIfPossible_removed() { // Prepare the activity record to be ready for immediate removal. It should be invisible and // have no process. Otherwise, request to finish it will send a message to client first. mActivity.setState(STOPPED, "test"); @@ -782,17 +791,427 @@ public class ActivityRecordTests extends ActivityTestsBase { // this will cause NPE when updating task's process. mActivity.app = null; assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REMOVED, - mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test", - false /* oomAdj */)); + mActivity.finishIfPossible("test", false /* oomAdj */)); assertTrue(mActivity.finishing); assertFalse(mActivity.isInStackLocked()); } + /** + * Verify that resumed activity is paused due to finish request. + */ + @Test + public void testFinishActivityIfPossible_resumedStartsPausing() { + mActivity.finishing = false; + mActivity.setState(RESUMED, "test"); + assertEquals("Currently resumed activity must be paused before removal", + FINISH_RESULT_REQUESTED, mActivity.finishIfPossible("test", false /* oomAdj */)); + assertEquals(PAUSING, mActivity.getState()); + verify(mActivity).setVisibility(eq(false)); + verify(mActivity.getDisplay().mDisplayContent) + .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */); + } + + /** + * Verify that finish request will be completed immediately for non-resumed activity. + */ + @Test + public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() { + final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED}; + for (ActivityState state : states) { + mActivity.finishing = false; + mActivity.setState(state, "test"); + reset(mActivity); + assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED, + mActivity.finishIfPossible("test", false /* oomAdj */)); + verify(mActivity).completeFinishing(anyString()); + } + } + + /** + * Verify that finishing will not be completed in PAUSING state. + */ + @Test + public void testFinishActivityIfPossible_pausing() { + mActivity.finishing = false; + mActivity.setState(PAUSING, "test"); + assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED, + mActivity.finishIfPossible("test", false /* oomAdj */)); + verify(mActivity, never()).completeFinishing(anyString()); + } + + /** + * Verify that finish request for resumed activity will prepare an app transition but not + * execute it immediately. + */ + @Test + public void testFinishActivityIfPossible_visibleResumedPreparesAppTransition() { + mActivity.finishing = false; + mActivity.visible = true; + mActivity.setState(RESUMED, "test"); + mActivity.finishIfPossible("test", false /* oomAdj */); + + verify(mActivity).setVisibility(eq(false)); + verify(mActivity.getDisplay().mDisplayContent) + .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */); + verify(mActivity.getDisplay().mDisplayContent, never()).executeAppTransition(); + } + + /** + * Verify that finish request for paused activity will prepare and execute an app transition. + */ + @Test + public void testFinishActivityIfPossible_visibleNotResumedExecutesAppTransition() { + mActivity.finishing = false; + mActivity.visible = true; + mActivity.setState(PAUSED, "test"); + mActivity.finishIfPossible("test", false /* oomAdj */); + + verify(mActivity).setVisibility(eq(false)); + verify(mActivity.getDisplay().mDisplayContent) + .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */); + verify(mActivity.getDisplay().mDisplayContent).executeAppTransition(); + } + + /** + * Verify that finish request for non-visible activity will not prepare any transitions. + */ + @Test + public void testFinishActivityIfPossible_nonVisibleNoAppTransition() { + // Put an activity on top of test activity to make it invisible and prevent us from + // accidentally resuming the topmost one again. + new ActivityBuilder(mService).build(); + mActivity.visible = false; + mActivity.setState(STOPPED, "test"); + + mActivity.finishIfPossible("test", false /* oomAdj */); + + verify(mActivity.getDisplay().mDisplayContent, never()) + .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */); + } + + /** + * Verify that complete finish request for non-finishing activity is invalid. + */ + @Test(expected = IllegalArgumentException.class) + public void testCompleteFinishing_failNotFinishing() { + mActivity.finishing = false; + mActivity.completeFinishing("test"); + } + + /** + * Verify that complete finish request for resumed activity is invalid. + */ + @Test(expected = IllegalArgumentException.class) + public void testCompleteFinishing_failResumed() { + mActivity.setState(RESUMED, "test"); + mActivity.completeFinishing("test"); + } + + /** + * Verify that finish request for pausing activity must be a no-op - activity will finish + * once it completes pausing. + */ + @Test + public void testCompleteFinishing_pausing() { + mActivity.setState(PAUSING, "test"); + mActivity.finishing = true; + + assertEquals("Activity must not be removed immediately - waiting for paused", + mActivity, mActivity.completeFinishing("test")); + assertEquals(PAUSING, mActivity.getState()); + verify(mActivity, never()).destroyIfPossible(anyString()); + } + + /** + * Verify that complete finish request for visible activity must be delayed before the next one + * becomes visible. + */ + @Test + public void testCompleteFinishing_waitForNextVisible() { + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.visible = true; + topActivity.nowVisible = true; + topActivity.finishing = true; + topActivity.setState(PAUSED, "true"); + // Mark the bottom activity as not visible, so that we will wait for it before removing + // the top one. + mActivity.visible = false; + mActivity.nowVisible = false; + mActivity.setState(STOPPED, "test"); + + assertEquals("Activity must not be removed immediately - waiting for next visible", + topActivity, topActivity.completeFinishing("test")); + assertEquals("Activity must be stopped to make next one visible", STOPPING, + topActivity.getState()); + assertTrue("Activity must be stopped to make next one visible", + topActivity.mStackSupervisor.mStoppingActivities.contains(topActivity)); + verify(topActivity, never()).destroyIfPossible(anyString()); + } + + /** + * Verify that complete finish request for invisible activity must not be delayed. + */ + @Test + public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() { + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.visible = false; + topActivity.nowVisible = false; + topActivity.finishing = true; + topActivity.setState(PAUSED, "true"); + // Mark the bottom activity as not visible, so that we would wait for it before removing + // the top one. + mActivity.visible = false; + mActivity.nowVisible = false; + mActivity.setState(STOPPED, "test"); + + topActivity.completeFinishing("test"); + + verify(topActivity).destroyIfPossible(anyString()); + } + + /** + * Verify that paused finishing activity will be added to finishing list and wait for next one + * to idle. + */ + @Test + public void testCompleteFinishing_waitForIdle() { + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.visible = true; + topActivity.nowVisible = true; + topActivity.finishing = true; + topActivity.setState(PAUSED, "true"); + // Mark the bottom activity as already visible, so that there is no need to wait for it. + mActivity.visible = true; + mActivity.nowVisible = true; + mActivity.setState(RESUMED, "test"); + + topActivity.completeFinishing("test"); + + verify(topActivity).addToFinishingAndWaitForIdle(); + } + + /** + * Verify that complete finish request for visible activity must not be delayed if the next one + * is already visible and it's not the focused stack. + */ + @Test + public void testCompleteFinishing_noWaitForNextVisible_stopped() { + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.visible = false; + topActivity.nowVisible = false; + topActivity.finishing = true; + topActivity.setState(STOPPED, "true"); + // Mark the bottom activity as already visible, so that there is no need to wait for it. + mActivity.visible = true; + mActivity.nowVisible = true; + mActivity.setState(RESUMED, "test"); + + topActivity.completeFinishing("test"); + + verify(topActivity).destroyIfPossible(anyString()); + } + + /** + * Verify that complete finish request for visible activity must not be delayed if the next one + * is already visible and it's not the focused stack. + */ + @Test + public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() { + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + topActivity.visible = true; + topActivity.nowVisible = true; + topActivity.finishing = true; + topActivity.setState(PAUSED, "true"); + // Mark the bottom activity as already visible, so that there is no need to wait for it. + mActivity.visible = true; + mActivity.nowVisible = true; + mActivity.setState(RESUMED, "test"); + + // Add another stack to become focused and make the activity there visible. This way it + // simulates finishing in non-focused stack in split-screen. + final ActivityStack stack = new StackBuilder(mRootActivityContainer).build(); + stack.getChildAt(0).getChildAt(0).nowVisible = true; + + topActivity.completeFinishing("test"); + + verify(topActivity).destroyIfPossible(anyString()); + } + + /** + * Verify destroy activity request completes successfully. + */ + @Test + public void testDestroyIfPossible() { + doReturn(false).when(mRootActivityContainer).resumeFocusedStacksTopActivities(); + spyOn(mStack); + mActivity.destroyIfPossible("test"); + + assertEquals(DESTROYING, mActivity.getState()); + assertTrue(mActivity.finishing); + verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + } + + /** + * Verify that complete finish request for visible activity must not destroy it immediately if + * it is the last running activity on a display with a home stack. We must wait for home + * activity to come up to avoid a black flash in this case. + */ + @Test + public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() { + // Empty the home stack. + final ActivityStack homeStack = mActivity.getDisplay().getHomeStack(); + for (TaskRecord t : homeStack.getAllTasks()) { + homeStack.removeTask(t, "test", REMOVE_TASK_MODE_DESTROYING); + } + mActivity.finishing = true; + doReturn(false).when(mRootActivityContainer).resumeFocusedStacksTopActivities(); + spyOn(mStack); + + // Try to destroy the last activity above the home stack. + mActivity.destroyIfPossible("test"); + + // Verify that the activity was not actually destroyed, but waits for next one to come up + // instead. + verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + assertEquals(FINISHING, mActivity.getState()); + assertTrue(mActivity.mStackSupervisor.mFinishingActivities.contains(mActivity)); + } + + /** + * Test that the activity will be moved to destroying state and the message to destroy will be + * sent to the client. + */ + @Test + public void testDestroyImmediately_hadApp_finishing() { + mActivity.finishing = true; + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYING, mActivity.getState()); + } + + /** + * Test that the activity will be moved to destroyed state immediately if it was not marked as + * finishing before {@link ActivityRecord#destroyImmediately(boolean, String)}. + */ + @Test + public void testDestroyImmediately_hadApp_notFinishing() { + mActivity.finishing = false; + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + } + + /** + * Test that an activity with no process attached and that is marked as finishing will be + * removed from task when {@link ActivityRecord#destroyImmediately(boolean, String)} is called. + */ + @Test + public void testDestroyImmediately_noApp_finishing() { + mActivity.app = null; + mActivity.finishing = true; + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertNull(mActivity.getTaskRecord()); + assertEquals(0, task.getChildCount()); + } + + /** + * Test that an activity with no process attached and that is not marked as finishing will be + * marked as DESTROYED but not removed from task. + */ + @Test + public void testDestroyImmediately_noApp_notFinishing() { + mActivity.app = null; + mActivity.finishing = false; + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertEquals(task, mActivity.getTaskRecord()); + assertEquals(1, task.getChildCount()); + } + + /** + * Test that an activity will not be destroyed if it is marked as non-destroyable. + */ + @Test + public void testSafelyDestroy_nonDestroyable() { + doReturn(false).when(mActivity).isDestroyable(); + + mActivity.safelyDestroy("test"); + + verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + } + + /** + * Test that an activity will not be destroyed if it is marked as non-destroyable. + */ + @Test + public void testSafelyDestroy_destroyable() { + doReturn(true).when(mActivity).isDestroyable(); + + mActivity.safelyDestroy("test"); + + verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + } + + @Test + public void testRemoveFromHistory() { + final ActivityStack stack = mActivity.getActivityStack(); + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.removeFromHistory("test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertNull(mActivity.app); + assertNull(mActivity.getTaskRecord()); + assertEquals(0, task.getChildCount()); + assertNull(task.getStack()); + assertEquals(0, stack.getChildCount()); + } + + /** + * Test that it's not allowed to call {@link ActivityRecord#destroyed(String)} if activity is + * not in destroying or destroyed state. + */ + @Test(expected = IllegalStateException.class) + public void testDestroyed_notDestroying() { + mActivity.setState(STOPPED, "test"); + mActivity.destroyed("test"); + } + + /** + * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroying + */ + @Test + public void testDestroyed_destroying() { + mActivity.setState(DESTROYING, "test"); + mActivity.destroyed("test"); + + verify(mActivity).removeFromHistory(anyString()); + } + + /** + * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroyed. + */ + @Test + public void testDestroyed_destroyed() { + mActivity.setState(DESTROYED, "test"); + mActivity.destroyed("test"); + + verify(mActivity).removeFromHistory(anyString()); + } + /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */ private void prepareFixedAspectRatioUnresizableActivity() { setupDisplayContentForCompatDisplayInsets(); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.maxAspectRatio = 1.5f; + mActivity.visible = true; ensureActivityConfiguration(); } @@ -805,8 +1224,12 @@ public class ActivityRecordTests extends ActivityTestsBase { final DisplayContent displayContent = mStack.getDisplay().mDisplayContent; displayContent.mBaseDisplayWidth = width; displayContent.mBaseDisplayHeight = height; - mTask.getWindowConfiguration().setAppBounds(0, 0, width, height); - mTask.getWindowConfiguration().setRotation(ROTATION_0); + final Configuration c = + new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration()); + c.windowConfiguration.setBounds(new Rect(0, 0, width, height)); + c.windowConfiguration.setAppBounds(0, 0, width, height); + c.windowConfiguration.setRotation(ROTATION_0); + mStack.getDisplay().onRequestedOverrideConfigurationChanged(c); return displayContent; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index ff7b1fadbaf9..60c5f0bd7188 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.Activity.RESULT_CANCELED; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -33,7 +32,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; -import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; @@ -235,7 +233,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; mStack.moveToFront("testStopActivityWithDestroy"); - mStack.stopActivityLocked(r); + r.stopIfPossible(); // Mostly testing to make sure there is a crash in the call part, so if we get here we are // good-to-go! } @@ -881,7 +879,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityRecord overlayActivity = new ActivityBuilder(mService).setTask(mTask) .setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build(); // If the task only remains overlay activity, the task should also be removed. - // See {@link ActivityStack#removeActivityFromHistoryLocked}. + // See {@link ActivityStack#removeFromHistory}. overlayActivity.mTaskOverlay = true; // The activity without an app means it will be removed immediately. @@ -996,8 +994,7 @@ public class ActivityStackTests extends ActivityTestsBase { homeStask.removeTask(homeTask, "testAdjustFocusedStack", REMOVE_TASK_MODE_DESTROYING); // Finish the only activity. - topActivity.finishActivityLocked(RESULT_CANCELED /* resultCode */, null /* resultData */, - "testAdjustFocusedStack", false /* oomAdj */); + topActivity.finishIfPossible("testAdjustFocusedStack", false /* oomAdj */); // Although home stack is empty, it should still be the focused stack. assertEquals(homeStask, mDefaultDisplay.getFocusedStack()); } @@ -1016,7 +1013,7 @@ public class ActivityStackTests extends ActivityTestsBase { } // Home stack should not be destroyed immediately. - final ActivityRecord activity1 = finishCurrentActivity(homeStack); + final ActivityRecord activity1 = finishTopActivity(homeStack); assertEquals(FINISHING, activity1.getState()); } @@ -1032,25 +1029,24 @@ public class ActivityStackTests extends ActivityTestsBase { // There is still an activity1 in stack1 so the activity2 should be added to finishing list // that will be destroyed until idle. stack2.getTopActivity().visible = true; - final ActivityRecord activity2 = finishCurrentActivity(stack2); + final ActivityRecord activity2 = finishTopActivity(stack2); assertEquals(STOPPING, activity2.getState()); assertThat(mSupervisor.mStoppingActivities).contains(activity2); // The display becomes empty. Since there is no next activity to be idle, the activity // should be destroyed immediately with updating configuration to restore original state. - final ActivityRecord activity1 = finishCurrentActivity(stack1); + final ActivityRecord activity1 = finishTopActivity(stack1); assertEquals(DESTROYING, activity1.getState()); verify(mRootActivityContainer).ensureVisibilityAndConfig(eq(null) /* starting */, eq(display.mDisplayId), anyBoolean(), anyBoolean()); } - private ActivityRecord finishCurrentActivity(ActivityStack stack) { + private ActivityRecord finishTopActivity(ActivityStack stack) { final ActivityRecord activity = stack.topRunningActivityLocked(); assertNotNull(activity); - activity.setState(PAUSED, "finishCurrentActivity"); + activity.setState(STOPPED, "finishTopActivity"); activity.makeFinishingLocked(); - activity.finishCurrentActivityLocked(ActivityRecord.FINISH_AFTER_VISIBLE, - false /* oomAdj */, "finishCurrentActivity"); + activity.completeFinishing("finishTopActivity"); return activity; } @@ -1113,6 +1109,19 @@ public class ActivityStackTests extends ActivityTestsBase { assertTrue(listener.mChanged); } + @Test + public void testResetTaskWithFinishingActivities() { + final ActivityRecord taskTop = + new ActivityBuilder(mService).setStack(mStack).setCreateTask(true).build(); + // Make all activities in the task are finishing to simulate TaskRecord#getTopActivity + // returns null. + taskTop.finishing = true; + + final ActivityRecord newR = new ActivityBuilder(mService).build(); + final ActivityRecord result = mStack.resetTaskIfNeededLocked(taskTop, newR); + assertThat(result).isEqualTo(taskTop); + } + private void verifyShouldSleepActivities(boolean focusedStack, boolean keyguardGoingAway, boolean displaySleeping, boolean expected) { final ActivityDisplay display = mock(ActivityDisplay.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index bcff70426c87..e6c9b9f7a160 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -249,6 +249,20 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } @Test + public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() { + final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay( + WINDOWING_MODE_FULLSCREEN); + final ActivityRecord source = createSourceActivity(fullscreenDisplay); + source.setWindowingMode(WINDOWING_MODE_FREEFORM); + + assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null, + mActivity, source, /* options */ null, mCurrent, mResult)); + + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, + WINDOWING_MODE_FULLSCREEN); + } + + @Test public void testKeepsPictureInPictureLaunchModeInOptions() { final TestActivityDisplay freeformDisplay = createNewActivityDisplay( WINDOWING_MODE_FREEFORM); @@ -571,6 +585,23 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } @Test + public void testRespectsLaunchBoundsWithFreeformSourceOnFullscreenDisplay() { + final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay( + WINDOWING_MODE_FULLSCREEN); + final ActivityRecord source = createSourceActivity(fullscreenDisplay); + source.setWindowingMode(WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + final Rect expected = new Rect(0, 0, 150, 150); + options.setLaunchBounds(expected); + + assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null, + mActivity, source, options, mCurrent, mResult)); + + assertEquals(expected, mResult.mBounds); + } + + @Test public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() { final TestActivityDisplay freeformDisplay = createNewActivityDisplay( WINDOWING_MODE_FREEFORM); diff --git a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java index 1e823b63d5b2..56dc3e05a240 100644 --- a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java +++ b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java @@ -185,12 +185,14 @@ public final class ByteStream { // Positive offsets only throw new IllegalArgumentException(); } - // do arithmetic and comparison in long to ovoid potention integer overflow + // do arithmetic and comparison in long to avoid potential integer overflow long longNewIndex = (long) mIndex + (long) numBytes; - if (longNewIndex < (long) mBytes.length) { + if (longNewIndex <= (long) mBytes.length) { mReadCount += numBytes; mIndex += numBytes; } else { + // Position the stream to the end so available() will return 0 + mIndex = mBytes.length; throw new IndexOutOfBoundsException(); } } @@ -210,6 +212,7 @@ public final class ByteStream { mReadCount -= numBytes; mIndex -= numBytes; } else { + mIndex = 0; throw new IndexOutOfBoundsException(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java index 7ebccf39868c..0535d717222e 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -45,7 +45,6 @@ abstract class UsbACEndpoint extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { mSubtype = stream.getByte(); - return mLength; } @@ -55,12 +54,21 @@ abstract class UsbACEndpoint extends UsbDescriptor { int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_AUDIOCONTROL"); + } return new UsbACAudioControlEndpoint(length, type, subClass); case AUDIO_AUDIOSTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_AUDIOSTREAMING"); + } return new UsbACAudioStreamEndpoint(length, type, subClass); case AUDIO_MIDISTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_MIDISTREAMING"); + } return new UsbACMidiEndpoint(length, type, subClass); default: diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java index 38c12a1f6c16..82fbfb89c498 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java @@ -100,8 +100,14 @@ public abstract class UsbACInterface extends UsbDescriptor { switch (subtype) { case ACI_HEADER: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_HEADER"); + } int acInterfaceSpec = stream.unpackUsbShort(); parser.setACInterfaceSpec(acInterfaceSpec); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACHeader(length, type, subtype, subClass, acInterfaceSpec); } else { @@ -111,7 +117,13 @@ public abstract class UsbACInterface extends UsbDescriptor { case ACI_INPUT_TERMINAL: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_INPUT_TERMINAL"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACInputTerminal(length, type, subtype, subClass); } else { @@ -121,7 +133,13 @@ public abstract class UsbACInterface extends UsbDescriptor { case ACI_OUTPUT_TERMINAL: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_OUTPUT_TERMINAL"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACOutputTerminal(length, type, subtype, subClass); } else { @@ -130,14 +148,26 @@ public abstract class UsbACInterface extends UsbDescriptor { } case ACI_SELECTOR_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_SELECTOR_UNIT"); + } return new UsbACSelectorUnit(length, type, subtype, subClass); case ACI_FEATURE_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_FEATURE_UNIT"); + } return new UsbACFeatureUnit(length, type, subtype, subClass); case ACI_MIXER_UNIT: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_MIXER_UNIT"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACMixerUnit(length, type, subtype, subClass); } else { @@ -215,14 +245,23 @@ public abstract class UsbACInterface extends UsbDescriptor { int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_AUDIOCONTROL"); + } return allocAudioControlDescriptor( parser, stream, length, type, subtype, subClass); case AUDIO_AUDIOSTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_AUDIOSTREAMING"); + } return allocAudioStreamingDescriptor( parser, stream, length, type, subtype, subClass); case AUDIO_MIDISTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_MIDISTREAMING"); + } return allocMidiStreamingDescriptor(length, type, subtype, subClass); default: diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java index 639aa4e03849..de2dd101acfd 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -30,7 +30,6 @@ import java.util.ArrayList; */ public final class UsbConfigDescriptor extends UsbDescriptor { private static final String TAG = "UsbConfigDescriptor"; - private static final boolean DEBUG = false; private int mTotalLength; // 2:2 Total length in bytes of data returned private byte mNumInterfaces; // 4:1 Number of Interfaces @@ -79,14 +78,14 @@ public final class UsbConfigDescriptor extends UsbDescriptor { } UsbConfiguration toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " toAndroid()"); } String name = parser.getDescriptorString(mConfigIndex); UsbConfiguration config = new UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower); UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()]; - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " " + mInterfaceDescriptors.size() + " interfaces."); } for (int index = 0; index < mInterfaceDescriptors.size(); index++) { diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java index ff67667e848d..44422a2d4603 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java @@ -43,7 +43,7 @@ public abstract class UsbDescriptor implements Reporting { protected int mHierarchyLevel; protected final int mLength; // 0:1 bLength Number Size of the Descriptor in Bytes (18 bytes) - // we store this as an int because Java bytes are SIGNED. + // we store this as an int because Java bytes are SIGNED. protected final byte mType; // 1:1 bDescriptorType Constant Device Descriptor (0x01) private byte[] mRawData; @@ -52,11 +52,11 @@ public abstract class UsbDescriptor implements Reporting { private static byte[] sStringBuffer = new byte[SIZE_STRINGBUFFER]; // Status - public static final int STATUS_UNPARSED = 0; - public static final int STATUS_PARSED_OK = 1; - public static final int STATUS_PARSED_UNDERRUN = 2; - public static final int STATUS_PARSED_OVERRUN = 3; - public static final int STATUS_PARSE_EXCEPTION = 4; + public static final int STATUS_UNPARSED = 0; + public static final int STATUS_PARSED_OK = 1; + public static final int STATUS_PARSED_UNDERRUN = 2; + public static final int STATUS_PARSED_OVERRUN = 3; + public static final int STATUS_PARSE_EXCEPTION = 4; private int mStatus = STATUS_UNPARSED; @@ -78,53 +78,53 @@ public abstract class UsbDescriptor implements Reporting { public static final byte DESCRIPTORTYPE_HID = 0x21; // 33 public static final byte DESCRIPTORTYPE_REPORT = 0x22; // 34 public static final byte DESCRIPTORTYPE_PHYSICAL = 0x23; // 35 - public static final byte DESCRIPTORTYPE_AUDIO_INTERFACE = 0x24; // 36 - public static final byte DESCRIPTORTYPE_AUDIO_ENDPOINT = 0x25; // 37 + public static final byte DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE = 0x24; // 36 + public static final byte DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT = 0x25; // 37 public static final byte DESCRIPTORTYPE_HUB = 0x29; // 41 public static final byte DESCRIPTORTYPE_SUPERSPEED_HUB = 0x2A; // 42 public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48 // Class IDs - public static final int CLASSID_DEVICE = 0x00; - public static final int CLASSID_AUDIO = 0x01; - public static final int CLASSID_COM = 0x02; - public static final int CLASSID_HID = 0x03; + public static final int CLASSID_DEVICE = 0x00; + public static final int CLASSID_AUDIO = 0x01; + public static final int CLASSID_COM = 0x02; + public static final int CLASSID_HID = 0x03; // public static final int CLASSID_??? = 0x04; - public static final int CLASSID_PHYSICAL = 0x05; - public static final int CLASSID_IMAGE = 0x06; - public static final int CLASSID_PRINTER = 0x07; - public static final int CLASSID_STORAGE = 0x08; - public static final int CLASSID_HUB = 0x09; - public static final int CLASSID_CDC_CONTROL = 0x0A; - public static final int CLASSID_SMART_CARD = 0x0B; + public static final int CLASSID_PHYSICAL = 0x05; + public static final int CLASSID_IMAGE = 0x06; + public static final int CLASSID_PRINTER = 0x07; + public static final int CLASSID_STORAGE = 0x08; + public static final int CLASSID_HUB = 0x09; + public static final int CLASSID_CDC_CONTROL = 0x0A; + public static final int CLASSID_SMART_CARD = 0x0B; //public static final int CLASSID_??? = 0x0C; - public static final int CLASSID_SECURITY = 0x0D; - public static final int CLASSID_VIDEO = 0x0E; - public static final int CLASSID_HEALTHCARE = 0x0F; - public static final int CLASSID_AUDIOVIDEO = 0x10; - public static final int CLASSID_BILLBOARD = 0x11; - public static final int CLASSID_TYPECBRIDGE = 0x12; - public static final int CLASSID_DIAGNOSTIC = 0xDC; - public static final int CLASSID_WIRELESS = 0xE0; - public static final int CLASSID_MISC = 0xEF; - public static final int CLASSID_APPSPECIFIC = 0xFE; + public static final int CLASSID_SECURITY = 0x0D; + public static final int CLASSID_VIDEO = 0x0E; + public static final int CLASSID_HEALTHCARE = 0x0F; + public static final int CLASSID_AUDIOVIDEO = 0x10; + public static final int CLASSID_BILLBOARD = 0x11; + public static final int CLASSID_TYPECBRIDGE = 0x12; + public static final int CLASSID_DIAGNOSTIC = 0xDC; + public static final int CLASSID_WIRELESS = 0xE0; + public static final int CLASSID_MISC = 0xEF; + public static final int CLASSID_APPSPECIFIC = 0xFE; public static final int CLASSID_VENDSPECIFIC = 0xFF; // Audio Subclass codes - public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; - public static final int AUDIO_AUDIOCONTROL = 0x01; - public static final int AUDIO_AUDIOSTREAMING = 0x02; - public static final int AUDIO_MIDISTREAMING = 0x03; + public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; + public static final int AUDIO_AUDIOCONTROL = 0x01; + public static final int AUDIO_AUDIOSTREAMING = 0x02; + public static final int AUDIO_MIDISTREAMING = 0x03; // Request IDs - public static final int REQUEST_GET_STATUS = 0x00; - public static final int REQUEST_CLEAR_FEATURE = 0x01; - public static final int REQUEST_SET_FEATURE = 0x03; - public static final int REQUEST_GET_ADDRESS = 0x05; - public static final int REQUEST_GET_DESCRIPTOR = 0x06; - public static final int REQUEST_SET_DESCRIPTOR = 0x07; - public static final int REQUEST_GET_CONFIGURATION = 0x08; - public static final int REQUEST_SET_CONFIGURATION = 0x09; + public static final int REQUEST_GET_STATUS = 0x00; + public static final int REQUEST_CLEAR_FEATURE = 0x01; + public static final int REQUEST_SET_FEATURE = 0x03; + public static final int REQUEST_GET_ADDRESS = 0x05; + public static final int REQUEST_GET_DESCRIPTOR = 0x06; + public static final int REQUEST_SET_DESCRIPTOR = 0x07; + public static final int REQUEST_GET_CONFIGURATION = 0x08; + public static final int REQUEST_SET_CONFIGURATION = 0x09; // USB control transfer timeout public static final int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200; @@ -163,7 +163,6 @@ public abstract class UsbDescriptor implements Reporting { public int getOverUnderRunCount() { return mOverUnderRunCount; } - public String getStatusString() { return sStatusStrings[mStatus]; } @@ -278,4 +277,24 @@ public abstract class UsbDescriptor implements Reporting { + " Len: " + getLength(); canvas.writeParagraph(text, false); } + + /* + * Logging Helpers + */ + static String getDescriptorName(byte descriptorType, int descriptorLength) { + String name = UsbStrings.getDescriptorName(descriptorType); + if (name != null) { + return name; + } else { + return "Unknown Descriptor Type " + descriptorType + + " 0x" + Integer.toHexString(descriptorType) + + " length:" + descriptorLength; + } + } + + static void logDescriptorName(byte descriptorType, int descriptorLength) { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "----> " + getDescriptorName(descriptorType, descriptorLength)); + } + } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index c02110186d8d..4f62d5a5c967 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -26,7 +26,7 @@ import java.util.ArrayList; */ public final class UsbDescriptorParser { private static final String TAG = "UsbDescriptorParser"; - private static final boolean DEBUG = false; + public static final boolean DEBUG = false; private final String mDeviceAddr; @@ -115,6 +115,8 @@ public final class UsbDescriptorParser { int length = stream.getUnsignedByte(); byte type = stream.getByte(); + UsbDescriptor.logDescriptorName(type, length); + UsbDescriptor descriptor = null; switch (type) { /* @@ -174,14 +176,56 @@ public final class UsbDescriptorParser { break; /* - * Audio Class Specific + * Various Class Specific */ - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: - descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE: + if (mCurInterfaceDescriptor != null) { + switch (mCurInterfaceDescriptor.getUsbClass()) { + case UsbDescriptor.CLASSID_AUDIO: + descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); + break; + + case UsbDescriptor.CLASSID_VIDEO: + Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO subType:0x" + + Integer.toHexString(stream.getByte())); + descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type); + break; + + case UsbDescriptor.CLASSID_AUDIOVIDEO: + Log.d(TAG, " UsbDescriptor.CLASSID_AUDIOVIDEO subType:0x" + + Integer.toHexString(stream.getByte())); + break; + + default: + Log.d(TAG, " Unparsed Class-specific Interface:0x" + + Integer.toHexString(mCurInterfaceDescriptor.getUsbClass())); + break; + } + } break; - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: - descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT: + if (mCurInterfaceDescriptor != null) { + switch (mCurInterfaceDescriptor.getUsbClass()) { + case UsbDescriptor.CLASSID_AUDIO: + descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + break; + case UsbDescriptor.CLASSID_VIDEO: + Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO subType:0x" + + Integer.toHexString(stream.getByte())); + descriptor = UsbVCEndpoint.allocDescriptor(this, length, type); + break; + + case UsbDescriptor.CLASSID_AUDIOVIDEO: + Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO subType:0x" + + Integer.toHexString(stream.getByte())); + break; + default: + Log.d(TAG, " Unparsed Class-specific Endpoint:0x" + + Integer.toHexString(mCurInterfaceDescriptor.getUsbClass())); + break; + } + } break; default: @@ -190,8 +234,6 @@ public final class UsbDescriptorParser { if (descriptor == null) { // Unknown Descriptor - Log.i(TAG, "Unknown Descriptor len: " + length + " type:0x" - + Integer.toHexString(type)); descriptor = new UsbUnknown(length, type); } @@ -210,10 +252,6 @@ public final class UsbDescriptorParser { * @hide */ public void parseDescriptors(byte[] descriptors) { - if (DEBUG) { - Log.d(TAG, "parseDescriptors() - start"); - } - ByteStream stream = new ByteStream(descriptors); while (stream.available() > 0) { UsbDescriptor descriptor = null; @@ -325,8 +363,8 @@ public final class UsbDescriptorParser { public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { - if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { - // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE + if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE) { + // ensure that this isn't an unrecognized DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE if (descriptor instanceof UsbACInterface) { UsbACInterface acDescriptor = (UsbACInterface) descriptor; if (acDescriptor.getSubtype() == subtype @@ -334,8 +372,8 @@ public final class UsbDescriptorParser { list.add(descriptor); } } else { - Log.w(TAG, "Unrecognized Audio Interface l: " + descriptor.getLength() - + " t:0x" + Integer.toHexString(descriptor.getType())); + Log.w(TAG, "Unrecognized Audio Interface len: " + descriptor.getLength() + + " type:0x" + Integer.toHexString(descriptor.getType())); } } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java index f50b9cbc5e3d..e6e10fef9f9a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java @@ -31,7 +31,6 @@ import java.util.ArrayList; */ public final class UsbDeviceDescriptor extends UsbDescriptor { private static final String TAG = "UsbDeviceDescriptor"; - private static final boolean DEBUG = false; public static final int USBSPEC_1_0 = 0x0100; public static final int USBSPEC_1_1 = 0x0110; @@ -136,19 +135,19 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { * @hide */ public UsbDevice.Builder toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid()"); } String mfgName = getMfgString(parser); String prodName = getProductString(parser); - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " mfgName:" + mfgName + " prodName:" + prodName); } String versionString = getDeviceReleaseString(); String serialStr = getSerialString(parser); - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " versionString:" + versionString + " serialStr:" + serialStr); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 4da31ea469c3..5eb0a2f75ded 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -27,7 +27,6 @@ import com.android.server.usb.descriptors.report.ReportCanvas; */ public class UsbEndpointDescriptor extends UsbDescriptor { private static final String TAG = "UsbEndpointDescriptor"; - private static final boolean DEBUG = false; public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111; public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000; @@ -110,7 +109,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { } /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() type:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE) + " sync:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_SYNCTYPE) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 632e3dc500fa..1dc6069bbb0a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -31,7 +31,6 @@ import java.util.ArrayList; */ public class UsbInterfaceDescriptor extends UsbDescriptor { private static final String TAG = "UsbInterfaceDescriptor"; - private static final boolean DEBUG = false; protected int mInterfaceNumber; // 2:1 Number of Interface protected byte mAlternateSetting; // 3:1 Value used to select alternative setting @@ -95,7 +94,7 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { } UsbInterface toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass) + " subclass:" + Integer.toHexString(mUsbSubclass) + " " + mEndpointDescriptors.size() + " endpoints."); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java new file mode 100644 index 000000000000..39fbc0d50b87 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java @@ -0,0 +1,61 @@ +/* + * 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 com.android.server.usb.descriptors; + +import android.util.Log; + +/** + * @hide + * A video class-specific Endpoint + * see + */ +abstract class UsbVCEndpoint extends UsbDescriptor { + private static final String TAG = "UsbVCEndpoint"; + + UsbVCEndpoint(int length, byte type, int subclass) { + super(length, type); + // mSubclass = subclass; + } + + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, + int length, byte type) { + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + int subClass = interfaceDesc.getUsbSubclass(); + switch (subClass) { +// case AUDIO_AUDIOCONTROL: +// if (UsbDescriptorParser.DEBUG) { +// Log.i(TAG, "---> AUDIO_AUDIOCONTROL"); +// } +// return new UsbACAudioControlEndpoint(length, type, subClass); +// +// case AUDIO_AUDIOSTREAMING: +// if (UsbDescriptorParser.DEBUG) { +// Log.i(TAG, "---> AUDIO_AUDIOSTREAMING"); +// } +// return new UsbACAudioStreamEndpoint(length, type, subClass); +// +// case AUDIO_MIDISTREAMING: +// if (UsbDescriptorParser.DEBUG) { +// Log.i(TAG, "---> AUDIO_MIDISTREAMING"); +// } +// return new UsbACMidiEndpoint(length, type, subClass); + + default: + Log.w(TAG, "Unknown Video Class Endpoint id:0x" + Integer.toHexString(subClass)); + return null; + } + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java new file mode 100644 index 000000000000..c9eb1ec36eb1 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java @@ -0,0 +1,112 @@ +/* + * 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 com.android.server.usb.descriptors; + +import android.util.Log; + +/** + * @hide + * A video class-specific Interface. + * see USB_Video_Class_1.1.pdf, section 3.7.2 + */ +public abstract class UsbVCInterface extends UsbDescriptor { + private static final String TAG = "UsbVCInterface"; + + // Class-specific Video Subtypes + public static final byte VCI_UNDEFINED = 0x00; + public static final byte VCI_VEADER = 0x01; + public static final byte VCI_INPUT_TERMINAL = 0x02; + public static final byte VCI_VOUTPUT_TERMINAL = 0x03; + public static final byte VCI_SELECTOR_UNIT = 0x04; + public static final byte VCI_VROCESSING_UNIT = 0x05; + public static final byte VCI_VEXTENSION_UNIT = 0x06; + + // See “Universal Serial Bus Device Class Definition for Video + protected final byte mSubtype; // 2:1 HEADER descriptor subtype + protected final int mSubclass; // from the mSubclass member of the + // "enclosing" Interface Descriptor + + public UsbVCInterface(int length, byte type, byte subtype, int subclass) { + super(length, type); + mSubtype = subtype; + mSubclass = subclass; + } + + /** + * Allocates an audio class interface subtype based on subtype and subclass. + */ + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, ByteStream stream, + int length, byte type) { + byte subtype = stream.getByte(); + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + int subClass = interfaceDesc.getUsbSubclass(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " Video Class-specific Interface subClass:0x" + + Integer.toHexString(subClass)); + } + switch (subClass) { + // TODO - Create descriptor classes and parse these... + case VCI_UNDEFINED: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_UNDEFINED"); + } + break; + + case VCI_VEADER: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_VEADER"); + } + break; + + case VCI_INPUT_TERMINAL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_INPUT_TERMINAL"); + } + break; + + case VCI_VOUTPUT_TERMINAL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_VOUTPUT_TERMINAL"); + } + break; + + case VCI_SELECTOR_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_SELECTOR_UNIT"); + } + break; + + case VCI_VROCESSING_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_VROCESSING_UNIT"); + } + break; + + case VCI_VEXTENSION_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_VEXTENSION_UNIT"); + } + break; + + default: + Log.w(TAG, "Unknown Video Class Interface Subclass: 0x" + + Integer.toHexString(subClass)); + return null; + } + + return null; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java index fb4576a6ee78..918ba2cc9249 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java @@ -56,9 +56,10 @@ public final class UsbStrings { sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HID, "HID"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_REPORT, "Report"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_PHYSICAL, "Physical"); - sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE, - "Audio Class Interface"); - sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT, "Audio Class Endpoint"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE, + "Class-specific Interface"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT, + "Class-specific Endpoint"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HUB, "Hub"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_SUPERSPEED_HUB, "Superspeed Hub"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT_COMPANION, diff --git a/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java b/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java index 1aa30fa94f42..72fa8977b675 100644 --- a/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java +++ b/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java @@ -126,11 +126,13 @@ public final class UsbDescriptorsTree { // // Audio Class Descriptors // - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: - addACInterface((UsbACInterface) descriptor); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE: + //TODO: This needs to be parsed out to Audio/Video... + // addACInterface((UsbACInterface) descriptor); break; - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT: + //TODO: This needs to be parsed out to Audio/Video... break; } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index b2ac5490440d..6132cc2250b9 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -52,8 +52,9 @@ import android.os.Parcel; import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; -import android.os.UserManager; +import android.os.UserManagerInternal; import android.provider.Settings; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -95,14 +96,14 @@ import java.util.concurrent.Executor; */ public class VoiceInteractionManagerService extends SystemService { static final String TAG = "VoiceInteractionManagerService"; - static final boolean DEBUG = true; // TODO(b/133242016) STOPSHIP: change to false before R ships + static final boolean DEBUG = false; final Context mContext; final ContentResolver mResolver; final DatabaseHelper mDbHelper; final ActivityManagerInternal mAmInternal; final ActivityTaskManagerInternal mAtmInternal; - final UserManager mUserManager; + final UserManagerInternal mUserManagerInternal; final ArraySet<Integer> mLoadedKeyphraseIds = new ArraySet<>(); ShortcutServiceInternal mShortcutServiceInternal; SoundTriggerInternal mSoundTriggerInternal; @@ -120,8 +121,8 @@ public class VoiceInteractionManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); mAtmInternal = Preconditions.checkNotNull( LocalServices.getService(ActivityTaskManagerInternal.class)); - mUserManager = Preconditions.checkNotNull( - context.getSystemService(UserManager.class)); + mUserManagerInternal = Preconditions.checkNotNull( + LocalServices.getService(UserManagerInternal.class)); PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( PermissionManagerServiceInternal.class); @@ -157,37 +158,30 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public boolean isSupported(UserInfo userInfo) { + return userInfo.isFull(); + } + + @Override public void onStartUser(@NonNull UserInfo userInfo) { - if (DEBUG) Slog.d(TAG, "onStartUser(" + userInfo + ")"); + if (DEBUG_USER) Slog.d(TAG, "onStartUser(" + userInfo + ")"); - if (!userInfo.isFull()) { - if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo); - return; - } mServiceStub.initForUser(userInfo.id); } @Override public void onUnlockUser(@NonNull UserInfo userInfo) { - if (DEBUG) Slog.d(TAG, "onUnlockUser(" + userInfo + ")"); + if (DEBUG_USER) Slog.d(TAG, "onUnlockUser(" + userInfo + ")"); - if (!userInfo.isFull()) { - if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo); - return; - } mServiceStub.initForUser(userInfo.id); mServiceStub.switchImplementationIfNeeded(false); } @Override - public void onSwitchUser(@NonNull UserInfo userInfo) { - if (DEBUG) Slog.d(TAG, "onSwitchUser(" + userInfo + ")"); + public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) { + if (DEBUG_USER) Slog.d(TAG, "onSwitchUser(" + from + " > " + to + ")"); - if (!userInfo.isFull()) { - if (DEBUG) Slog.d(TAG, "***** skipping on non-full user " + userInfo); - return; - } - mServiceStub.switchUser(userInfo.id); + mServiceStub.switchUser(to.id); } class LocalService extends VoiceInteractionManagerInternal { @@ -225,6 +219,7 @@ public class VoiceInteractionManagerService extends SystemService { private boolean mSafeMode; private int mCurUser; private boolean mCurUserUnlocked; + private boolean mCurUserSupported; private final boolean mEnableService; VoiceInteractionManagerServiceStub() { @@ -292,9 +287,9 @@ public class VoiceInteractionManagerService extends SystemService { public void initForUser(int userHandle) { final TimingsTraceAndSlog t; - if (DEBUG) { - t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("VoiceInteractionSvc.initForUser(" + userHandle + ")"); + if (DEBUG_USER) { + t = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + t.traceBegin("initForUser(" + userHandle + ")"); } else { t = null; } @@ -439,15 +434,21 @@ public class VoiceInteractionManagerService extends SystemService { new SettingsObserver(UiThread.getHandler()); synchronized (this) { - mCurUser = ActivityManager.getCurrentUser(); + setCurrentUserLocked(ActivityManager.getCurrentUser()); switchImplementationIfNeededLocked(false); } } - public void switchUser(int userHandle) { + private void setCurrentUserLocked(@UserIdInt int userHandle) { + mCurUser = userHandle; + final UserInfo userInfo = mUserManagerInternal.getUserInfo(mCurUser); + mCurUserSupported = isSupported(userInfo); + } + + public void switchUser(@UserIdInt int userHandle) { FgThread.getHandler().post(() -> { synchronized (this) { - mCurUser = userHandle; + setCurrentUserLocked(userHandle); mCurUserUnlocked = false; switchImplementationIfNeededLocked(false); } @@ -461,10 +462,22 @@ public class VoiceInteractionManagerService extends SystemService { } void switchImplementationIfNeededLocked(boolean force) { + if (!mCurUserSupported) { + if (DEBUG_USER) { + Slog.d(TAG, "switchImplementationIfNeeded(): skipping on unsuported user " + + mCurUser); + } + if (mImpl != null) { + mImpl.shutdownLocked(); + setImplLocked(null); + } + return; + } + final TimingsTraceAndSlog t; - if (DEBUG) { - t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("VoiceInteractionSvc.switchImplementation(" + mCurUser + ")"); + if (DEBUG_USER) { + t = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + t.traceBegin("switchImplementation(" + mCurUser + ")"); } else { t = null; } @@ -494,7 +507,7 @@ public class VoiceInteractionManagerService extends SystemService { final boolean hasComponent = serviceComponent != null && serviceInfo != null; - if (mUserManager.isUserUnlockingOrUnlocked(mCurUser)) { + if (mUserManagerInternal.isUserUnlockingOrUnlocked(mCurUser)) { if (hasComponent) { mShortcutServiceInternal.setShortcutHostPackage(TAG, serviceComponent.getPackageName(), mCurUser); @@ -1275,6 +1288,9 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (this) { pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)"); pw.println(" mEnableService: " + mEnableService); + pw.println(" mCurUser: " + mCurUser); + pw.println(" mCurUserUnlocked: " + mCurUserUnlocked); + pw.println(" mCurUserSupported: " + mCurUserSupported); if (mImpl == null) { pw.println(" (No active implementation)"); return; @@ -1330,9 +1346,12 @@ public class VoiceInteractionManagerService extends SystemService { RoleObserver(@NonNull @CallbackExecutor Executor executor) { mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL); - UserHandle currentUser = UserHandle.of(LocalServices.getService( - ActivityManagerInternal.class).getCurrentUserId()); - onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT, currentUser); + // Sync only if assistant role has been initialized. + if (mRm.isRoleAvailable(RoleManager.ROLE_ASSISTANT)) { + UserHandle currentUser = UserHandle.of(LocalServices.getService( + ActivityManagerInternal.class).getCurrentUserId()); + onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT, currentUser); + } } private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) { diff --git a/startop/scripts/app_startup/lib/adb_utils.py b/startop/scripts/app_startup/lib/adb_utils.py index 1c60a17ada1b..e56a96848895 100644 --- a/startop/scripts/app_startup/lib/adb_utils.py +++ b/startop/scripts/app_startup/lib/adb_utils.py @@ -42,6 +42,8 @@ def logcat_save_timestamp() -> str: def vm_drop_cache(): """Free pagecache and slab object.""" cmd_utils.run_adb_shell_command('echo 3 > /proc/sys/vm/drop_caches') + # Sleep a little bit to provide enougth time for cache cleanup. + time.sleep(2) def root(): """Roots adb and successive adb commands will run under root.""" diff --git a/startop/scripts/iorap/compiler.py b/startop/scripts/iorap/compiler.py index ef9b870b8113..c940fe988855 100755 --- a/startop/scripts/iorap/compiler.py +++ b/startop/scripts/iorap/compiler.py @@ -29,6 +29,7 @@ import re import sys import tempfile from pathlib import Path +from datetime import timedelta from typing import Iterable, Optional, List DIR = os.path.abspath(os.path.dirname(__file__)) @@ -173,7 +174,7 @@ def build_protobuf(page_runs, inode2filename, filters=[]): return trace_file def calc_trace_end_time(trace2db: Trace2Db, - trace_duration: Optional[int]) -> float: + trace_duration: Optional[timedelta]) -> float: """ Calculates the end time based on the trace duration. The start time is the first receiving mm file map event. @@ -189,9 +190,9 @@ def calc_trace_end_time(trace2db: Trace2Db, MmFilemapAddToPageCache.raw_ftrace_entry).order_by( RawFtraceEntry.timestamp).first() - return first_event.raw_ftrace_entry.timestamp + trace_duration + return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds() -def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[int]): +def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]): end_time = calc_trace_end_time(trace2db, trace_duration) # SELECT * FROM tbl ORDER BY id; return trace2db.session.query(MmFilemapAddToPageCache).join( @@ -210,7 +211,7 @@ def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str, def run(sql_db_path:str, trace_file:str, - trace_duration:Optional[int], + trace_duration:Optional[timedelta], output_file:str, inode_table:str, filter:List[str]) -> int: @@ -292,11 +293,14 @@ def main(argv): if options.sql_db: sql_db_path = options.sql_db + trace_duration = timedelta(milliseconds=options.trace_duration) if \ + options.trace_duration is not None else None + # if the input is systrace if options.trace_file: return run(sql_db_path, options.trace_file, - options.trace_duration, + trace_duration, options.output_file, inode_table, options.filter) @@ -308,7 +312,7 @@ def main(argv): trace_file.name) return run(sql_db_path, trace_file.name, - options.trace_duration, + trace_duration, options.output_file, inode_table, options.filter) diff --git a/startop/scripts/iorap/compiler_test.py b/startop/scripts/iorap/compiler_test.py index b5d28b03a8d5..1a9f059fc6b6 100644 --- a/startop/scripts/iorap/compiler_test.py +++ b/startop/scripts/iorap/compiler_test.py @@ -70,9 +70,9 @@ def test_compiler_main(tmpdir): # 10ms duration expected = os.path.join(DIR, 'test_fixtures/compiler/test_result_with_duration.TraceFile.pb') - assert_compile_result(output, expected, '--duration', '10') + assert_compile_result(output, expected, '--duration', '10000') # 30ms duration expected = os.path.join(DIR, 'test_fixtures/compiler/test_result_without_duration.TraceFile.pb') - assert_compile_result(output, expected, '--duration', '30') + assert_compile_result(output, expected, '--duration', '30000') diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 42a5501cc229..0f1ae3de6669 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -4058,6 +4058,53 @@ public final class Telephony { public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC"; /** + * The timestamp in millisecond of when the device received the message. + * <P>Type: BIGINT</P> + */ + public static final String RECEIVED_TIME = "received_time"; + + /** + * Indicates that whether the message has been broadcasted to the application. + * <P>Type: BOOLEAN</P> + */ + public static final String MESSAGE_BROADCASTED = "message_broadcasted"; + + /** + * The Warning Area Coordinates Elements. This element is used for geo-fencing purpose. + * + * The geometry and its coordinates are separated vertical bar, the first item is the + * geometry type and the remaining items are the parameter of this geometry. + * + * Only circle and polygon are supported. The coordinates are represented in Horizontal + * coordinates format. + * + * Coordinate encoding: + * "latitude, longitude" + * where -90.00000 <= latitude <= 90.00000 and -180.00000 <= longitude <= 180.00000 + * + * Polygon encoding: + * "polygon|lat1,lng1|lat2,lng2|...|latn,lngn" + * lat1,lng1 ... latn,lngn are the vertices coordinate of the polygon. + * + * Circle encoding: + * "circle|lat,lng|radius". + * lat,lng is the center of the circle. The unit of circle's radius is meter. + * + * Example: + * "circle|0,0|100" mean a circle which center located at (0,0) and its radius is 100 meter. + * "polygon|0,1.5|0,1|1,1|1,0" mean a polygon has vertices (0,1.5),(0,1),(1,1),(1,0). + * + * There could be more than one geometry store in this field, they are separated by a + * semicolon. + * + * Example: + * "circle|0,0|100;polygon|0,0|0,1.5|1,1|1,0;circle|100.123,100|200.123" + * + * <P>Type: TEXT</P> + */ + public static final String GEOMETRIES = "geometries"; + + /** * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects. */ public static final String[] QUERY_COLUMNS = { @@ -4082,6 +4129,33 @@ public final class Telephony { CMAS_URGENCY, CMAS_CERTAINTY }; + + /** + * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects. + */ + public static final String[] QUERY_COLUMNS_FWK = { + _ID, + GEOGRAPHICAL_SCOPE, + PLMN, + LAC, + CID, + SERIAL_NUMBER, + SERVICE_CATEGORY, + LANGUAGE_CODE, + MESSAGE_BODY, + MESSAGE_FORMAT, + MESSAGE_PRIORITY, + ETWS_WARNING_TYPE, + CMAS_MESSAGE_CLASS, + CMAS_CATEGORY, + CMAS_RESPONSE_TYPE, + CMAS_SEVERITY, + CMAS_URGENCY, + CMAS_CERTAINTY, + RECEIVED_TIME, + MESSAGE_BROADCASTED, + GEOMETRIES + }; } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 10d4b8dbf33e..654b54d988cf 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1026,6 +1026,18 @@ public class CarrierConfigManager { "call_forwarding_map_non_number_to_voicemail_bool"; /** + * When {@code true}, the phone will always tell the IMS stack to keep RTT enabled and + * determine on a per-call basis (based on extras from the dialer app) whether a call should be + * an RTT call or not. + * + * When {@code false}, the old behavior is used, where the toggle in accessibility settings is + * used to set the IMS stack's RTT enabled state. + * @hide + */ + public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = + "ignore_rtt_mode_setting_bool"; + + /** * Determines whether conference calls are supported by a carrier. When {@code true}, * conference calling is supported, {@code false otherwise}. */ @@ -1391,6 +1403,13 @@ public class CarrierConfigManager { "read_only_apn_fields_string_array"; /** + * Default value of APN types field if not specified by user when adding/modifying an APN. + * @hide + */ + public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = + "apn_settings_default_apn_types_string_array"; + + /** * Boolean indicating if intent for emergency call state changes should be broadcast * @hide */ @@ -2678,10 +2697,12 @@ public class CarrierConfigManager { * the value is the icon name. Use "None" as the icon name if no icon should be shown in a * specific 5G scenario. If the scenario is "None", config can skip this key and value. * + * Icon name options: "5G_Plus", "5G". + * * Here is an example: - * UE want to display 5GPlus icon for scenario#1, and 5G icon for scenario#2; otherwise no + * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no * define. - * The configuration is: "connected_mmwave:5GPlus,connected:5G" + * The configuration is: "connected_mmwave:5G_Plus,connected:5G" * * @hide */ @@ -3261,6 +3282,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true); sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, new String[] {"dun"}); sDefaults.putStringArray(KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, null); sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true); @@ -3304,6 +3326,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0); sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100); sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false); + sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false); sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); @@ -3551,7 +3574,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_USE_USIM_BOOL, false); sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, false); - sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false); + sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, true); sDefaults.putString(KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING, ""); sDefaults.putBoolean(KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN, false); diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 1c64bcd28966..89b96654451e 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -24,7 +24,6 @@ import android.telephony.NetworkService.NetworkServiceProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; /** * Network service callback. Object of this class is passed to NetworkServiceProvider upon @@ -61,11 +60,11 @@ public class NetworkServiceCallback { /** Request failed */ public static final int RESULT_ERROR_FAILED = 5; - private final WeakReference<INetworkServiceCallback> mCallback; + private final INetworkServiceCallback mCallback; /** @hide */ public NetworkServiceCallback(INetworkServiceCallback callback) { - mCallback = new WeakReference<>(callback); + mCallback = callback; } /** @@ -78,15 +77,14 @@ public class NetworkServiceCallback { */ public void onRequestNetworkRegistrationInfoComplete(int result, @Nullable NetworkRegistrationInfo state) { - INetworkServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onRequestNetworkRegistrationInfoComplete(result, state); + mCallback.onRequestNetworkRegistrationInfoComplete(result, state); } catch (RemoteException e) { Rlog.e(mTag, "Failed to onRequestNetworkRegistrationInfoComplete on the remote"); } } else { - Rlog.e(mTag, "Weak reference of callback is null."); + Rlog.e(mTag, "onRequestNetworkRegistrationInfoComplete callback is null."); } } }
\ No newline at end of file diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 36e81232100c..bb2269fc4d00 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -551,7 +551,6 @@ public class SubscriptionInfo implements Parcelable { * * @param context Context of the application to check. * @return whether the app is authorized to manage this subscription per its metadata. - * @throws UnsupportedOperationException if this subscription is not embedded. * @hide * @deprecated - Do not use. */ @@ -567,15 +566,11 @@ public class SubscriptionInfo implements Parcelable { * @param context Any context. * @param packageName Package name of the app to check. * @return whether the app is authorized to manage this subscription per its metadata. - * @throws UnsupportedOperationException if this subscription is not embedded. * @hide * @deprecated - Do not use. */ @Deprecated public boolean canManageSubscription(Context context, String packageName) { - if (!isEmbedded()) { - throw new UnsupportedOperationException("Not an embedded subscription"); - } List<UiccAccessRule> allAccessRules = getAllAccessRules(); if (allAccessRules == null) { return false; @@ -606,9 +601,6 @@ public class SubscriptionInfo implements Parcelable { */ @SystemApi public @Nullable List<UiccAccessRule> getAccessRules() { - if (!isEmbedded()) { - throw new UnsupportedOperationException("Not an embedded subscription"); - } if (mNativeAccessRules == null) return null; return Arrays.asList(mNativeAccessRules); } @@ -619,9 +611,6 @@ public class SubscriptionInfo implements Parcelable { * @hide */ public @Nullable List<UiccAccessRule> getAllAccessRules() { - if (!isEmbedded()) { - throw new UnsupportedOperationException("Not an embedded subscription"); - } List<UiccAccessRule> merged = new ArrayList<>(); if (mNativeAccessRules != null) merged.addAll(getAccessRules()); if (mCarrierConfigAccessRules != null) { diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index a84c916dde34..5e47e498ee10 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2588,7 +2588,6 @@ public class SubscriptionManager { * * @param info The subscription to check. * @return whether the app is authorized to manage this subscription per its metadata. - * @throws IllegalArgumentException if this subscription is not embedded. */ public boolean canManageSubscription(SubscriptionInfo info) { return canManageSubscription(info, mContext.getPackageName()); @@ -2604,13 +2603,9 @@ public class SubscriptionManager { * @param info The subscription to check. * @param packageName Package name of the app to check. * @return whether the app is authorized to manage this subscription per its access rules. - * @throws IllegalArgumentException if this subscription is not embedded. * @hide */ public boolean canManageSubscription(SubscriptionInfo info, String packageName) { - if (!info.isEmbedded()) { - throw new IllegalArgumentException("Not an embedded subscription"); - } if (info.getAllAccessRules() == null) { return false; } @@ -2857,8 +2852,7 @@ public class SubscriptionManager { * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. - * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist, - * or the groupUuid doesn't exist. + * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist. * @throws IllegalStateException if Telephony service is in bad state. * * @param subIdList list of subId that need adding into the group @@ -3012,7 +3006,7 @@ public class SubscriptionManager { // to the caller. boolean hasCarrierPrivilegePermission = TelephonyManager.from(mContext) .hasCarrierPrivileges(info.getSubscriptionId()) - || (info.isEmbedded() && canManageSubscription(info)); + || canManageSubscription(info); return hasCarrierPrivilegePermission; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 35b435d8dfb7..553bff26f78f 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -11047,6 +11047,8 @@ public class TelephonyManager { * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current * network returned by {@link #getNetworkCountryIso()}. * + * <p>There may be a delay of several minutes before reporting that no country is detected. + * * @see #EXTRA_NETWORK_COUNTRY * @see #getNetworkCountryIso() */ diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 89d30c0d4373..11dc78a611ff 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -27,7 +27,6 @@ import android.telephony.data.DataService.DataServiceProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; import java.util.List; /** @@ -64,11 +63,11 @@ public class DataServiceCallback { /** Request sent in illegal state */ public static final int RESULT_ERROR_ILLEGAL_STATE = 4; - private final WeakReference<IDataServiceCallback> mCallback; + private final IDataServiceCallback mCallback; /** @hide */ public DataServiceCallback(IDataServiceCallback callback) { - mCallback = new WeakReference<>(callback); + mCallback = callback; } /** @@ -80,14 +79,15 @@ public class DataServiceCallback { */ public void onSetupDataCallComplete(@ResultCode int result, @Nullable DataCallResponse response) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { if (DBG) Rlog.d(TAG, "onSetupDataCallComplete"); - callback.onSetupDataCallComplete(result, response); + mCallback.onSetupDataCallComplete(result, response); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetupDataCallComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetupDataCallComplete: callback is null!"); } } @@ -98,14 +98,15 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onDeactivateDataCallComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { if (DBG) Rlog.d(TAG, "onDeactivateDataCallComplete"); - callback.onDeactivateDataCallComplete(result); + mCallback.onDeactivateDataCallComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onDeactivateDataCallComplete on the remote"); } + } else { + Rlog.e(TAG, "onDeactivateDataCallComplete: callback is null!"); } } @@ -116,13 +117,14 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onSetInitialAttachApnComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onSetInitialAttachApnComplete(result); + mCallback.onSetInitialAttachApnComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetInitialAttachApnComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetInitialAttachApnComplete: callback is null!"); } } @@ -133,13 +135,14 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onSetDataProfileComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onSetDataProfileComplete(result); + mCallback.onSetDataProfileComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetDataProfileComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetDataProfileComplete: callback is null!"); } } @@ -153,13 +156,14 @@ public class DataServiceCallback { */ public void onRequestDataCallListComplete(@ResultCode int result, @NonNull List<DataCallResponse> dataCallList) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onRequestDataCallListComplete(result, dataCallList); + mCallback.onRequestDataCallListComplete(result, dataCallList); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onRequestDataCallListComplete on the remote"); } + } else { + Rlog.e(TAG, "onRequestDataCallListComplete: callback is null!"); } } @@ -170,14 +174,15 @@ public class DataServiceCallback { * @param dataCallList List of the current active data connection. */ public void onDataCallListChanged(@NonNull List<DataCallResponse> dataCallList) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { if (DBG) Rlog.d(TAG, "onDataCallListChanged"); - callback.onDataCallListChanged(dataCallList); + mCallback.onDataCallListChanged(dataCallList); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onDataCallListChanged on the remote"); } + } else { + Rlog.e(TAG, "onDataCallListChanged: callback is null!"); } } } diff --git a/telephony/java/com/android/internal/telephony/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java new file mode 100644 index 000000000000..73dd822903f5 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java @@ -0,0 +1,369 @@ +/* + * 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 com.android.internal.telephony; + +import android.annotation.NonNull; +import android.telephony.Rlog; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * This utils class is specifically used for geo-targeting of CellBroadcast messages. + * The coordinates used by this utils class are latitude and longitude, but some algorithms in this + * class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use + * this class for anything other then geo-targeting of cellbroadcast messages. + */ +public class CbGeoUtils { + /** Geometric interface. */ + public interface Geometry { + /** + * Determines if the given point {@code p} is inside the geometry. + * @param p point in latitude, longitude format. + * @return {@code True} if the given point is inside the geometry. + */ + boolean contains(LatLng p); + } + + /** + * Tolerance for determining if the value is 0. If the absolute value of a value is less than + * this tolerance, it will be treated as 0. + */ + public static final double EPS = 1e-7; + + /** The radius of earth. */ + public static final int EARTH_RADIUS_METER = 6371 * 1000; + + private static final String TAG = "CbGeoUtils"; + + /** The TLV tags of WAC, defined in ATIS-0700041 5.2.3 WAC tag coding. */ + public static final int GEO_FENCING_MAXIMUM_WAIT_TIME = 0x01; + public static final int GEOMETRY_TYPE_POLYGON = 0x02; + public static final int GEOMETRY_TYPE_CIRCLE = 0x03; + + /** The identifier of geometry in the encoded string. */ + private static final String CIRCLE_SYMBOL = "circle"; + private static final String POLYGON_SYMBOL = "polygon"; + + /** Point represent by (latitude, longitude). */ + public static class LatLng { + public final double lat; + public final double lng; + + /** + * Constructor. + * @param lat latitude, range [-90, 90] + * @param lng longitude, range [-180, 180] + */ + public LatLng(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + + /** + * @param p the point use to calculate the subtraction result. + * @return the result of this point subtract the given point {@code p}. + */ + public LatLng subtract(LatLng p) { + return new LatLng(lat - p.lat, lng - p.lng); + } + + /** + * Calculate the distance in meter between this point and the given point {@code p}. + * @param p the point use to calculate the distance. + * @return the distance in meter. + */ + public double distance(LatLng p) { + double dlat = Math.sin(0.5 * Math.toRadians(lat - p.lat)); + double dlng = Math.sin(0.5 * Math.toRadians(lng - p.lng)); + double x = dlat * dlat + + dlng * dlng * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(p.lat)); + return 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)) * EARTH_RADIUS_METER; + } + + @Override + public String toString() { + return "(" + lat + "," + lng + ")"; + } + } + + /** + * The class represents a simple polygon with at least 3 points. + */ + public static class Polygon implements Geometry { + /** + * In order to reduce the loss of precision in floating point calculations, all vertices + * of the polygon are scaled. Set the value of scale to 1000 can take into account the + * actual distance accuracy of 1 meter if the EPS is 1e-7 during the calculation. + */ + private static final double SCALE = 1000.0; + + private final List<LatLng> mVertices; + private final List<Point> mScaledVertices; + private final LatLng mOrigin; + + /** + * Constructs a simple polygon from the given vertices. The adjacent two vertices are + * connected to form an edge of the polygon. The polygon has at least 3 vertices, and the + * last vertices and the first vertices must be adjacent. + * + * The longitude difference in the vertices should be less than 180 degree. + */ + public Polygon(@NonNull List<LatLng> vertices) { + mVertices = vertices; + + // Find the point with smallest longitude as the mOrigin point. + int idx = 0; + for (int i = 1; i < vertices.size(); i++) { + if (vertices.get(i).lng < vertices.get(idx).lng) { + idx = i; + } + } + mOrigin = vertices.get(idx); + + mScaledVertices = vertices.stream() + .map(latLng -> convertAndScaleLatLng(latLng)) + .collect(Collectors.toList()); + } + + public List<LatLng> getVertices() { + return mVertices; + } + + /** + * Check if the given point {@code p} is inside the polygon. This method counts the number + * of times the polygon winds around the point P, A.K.A "winding number". The point is + * outside only when this "winding number" is 0. + * + * If a point is on the edge of the polygon, it is also considered to be inside the polygon. + */ + @Override + public boolean contains(LatLng latLng) { + Point p = convertAndScaleLatLng(latLng); + + int n = mScaledVertices.size(); + int windingNumber = 0; + for (int i = 0; i < n; i++) { + Point a = mScaledVertices.get(i); + Point b = mScaledVertices.get((i + 1) % n); + + // CCW is counterclockwise + // CCW = ab x ap + // CCW > 0 -> ap is on the left side of ab + // CCW == 0 -> ap is on the same line of ab + // CCW < 0 -> ap is on the right side of ab + int ccw = sign(crossProduct(b.subtract(a), p.subtract(a))); + + if (ccw == 0) { + if (Math.min(a.x, b.x) <= p.x && p.x <= Math.max(a.x, b.x) + && Math.min(a.y, b.y) <= p.y && p.y <= Math.max(a.y, b.y)) { + return true; + } + } else { + if (sign(a.y - p.y) <= 0) { + // upward crossing + if (ccw > 0 && sign(b.y - p.y) > 0) { + ++windingNumber; + } + } else { + // downward crossing + if (ccw < 0 && sign(b.y - p.y) <= 0) { + --windingNumber; + } + } + } + } + return windingNumber != 0; + } + + /** + * Move the given point {@code latLng} to the coordinate system with {@code mOrigin} as the + * origin and scale it. {@code mOrigin} is selected from the vertices of a polygon, it has + * the smallest longitude value among all of the polygon vertices. + * + * @param latLng the point need to be converted and scaled. + * @Return a {@link Point} object. + */ + private Point convertAndScaleLatLng(LatLng latLng) { + double x = latLng.lat - mOrigin.lat; + double y = latLng.lng - mOrigin.lng; + + // If the point is in different hemispheres(western/eastern) than the mOrigin, and the + // edge between them cross the 180th meridian, then its relative coordinates will be + // extended. + // For example, suppose the longitude of the mOrigin is -178, and the longitude of the + // point to be converted is 175, then the longitude after the conversion is -8. + // calculation: (-178 - 8) - (-178). + if (sign(mOrigin.lng) != 0 && sign(mOrigin.lng) != sign(latLng.lng)) { + double distCross0thMeridian = Math.abs(mOrigin.lng) + Math.abs(latLng.lng); + if (sign(distCross0thMeridian * 2 - 360) > 0) { + y = sign(mOrigin.lng) * (360 - distCross0thMeridian); + } + } + return new Point(x * SCALE, y * SCALE); + } + + private static double crossProduct(Point a, Point b) { + return a.x * b.y - a.y * b.x; + } + + static final class Point { + public final double x; + public final double y; + + Point(double x, double y) { + this.x = x; + this.y = y; + } + + public Point subtract(Point p) { + return new Point(x - p.x, y - p.y); + } + } + } + + /** The class represents a circle. */ + public static class Circle implements Geometry { + private final LatLng mCenter; + private final double mRadiusMeter; + + public Circle(LatLng center, double radiusMeter) { + this.mCenter = center; + this.mRadiusMeter = radiusMeter; + } + + public LatLng getCenter() { + return mCenter; + } + + public double getRadius() { + return mRadiusMeter; + } + + @Override + public boolean contains(LatLng p) { + return mCenter.distance(p) <= mRadiusMeter; + } + } + + /** + * Parse the geometries from the encoded string {@code str}. The string must follow the + * geometry encoding specified by {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + */ + @NonNull + public static List<Geometry> parseGeometriesFromString(@NonNull String str) { + List<Geometry> geometries = new ArrayList<>(); + for (String geometryStr : str.split("\\s*;\\s*")) { + String[] geoParameters = geometryStr.split("\\s*\\|\\s*"); + switch (geoParameters[0]) { + case CIRCLE_SYMBOL: + geometries.add(new Circle(parseLatLngFromString(geoParameters[1]), + Double.parseDouble(geoParameters[2]))); + break; + case POLYGON_SYMBOL: + List<LatLng> vertices = new ArrayList<>(geoParameters.length - 1); + for (int i = 1; i < geoParameters.length; i++) { + vertices.add(parseLatLngFromString(geoParameters[i])); + } + geometries.add(new Polygon(vertices)); + break; + default: + Rlog.e(TAG, "Invalid geometry format " + geometryStr); + } + } + return geometries; + } + + /** + * Encode a list of geometry objects to string. The encoding format is specified by + * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + * + * @param geometries the list of geometry objects need to be encoded. + * @return the encoded string. + */ + @NonNull + public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) { + return geometries.stream() + .map(geometry -> encodeGeometryToString(geometry)) + .filter(encodedStr -> !TextUtils.isEmpty(encodedStr)) + .collect(Collectors.joining(";")); + } + + + /** + * Encode the geometry object to string. The encoding format is specified by + * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + * @param geometry the geometry object need to be encoded. + * @return the encoded string. + */ + @NonNull + private static String encodeGeometryToString(@NonNull Geometry geometry) { + StringBuilder sb = new StringBuilder(); + if (geometry instanceof Polygon) { + sb.append(POLYGON_SYMBOL); + for (LatLng latLng : ((Polygon) geometry).getVertices()) { + sb.append("|"); + sb.append(latLng.lat); + sb.append(","); + sb.append(latLng.lng); + } + } else if (geometry instanceof Circle) { + sb.append(CIRCLE_SYMBOL); + Circle circle = (Circle) geometry; + + // Center + sb.append("|"); + sb.append(circle.getCenter().lat); + sb.append(","); + sb.append(circle.getCenter().lng); + + // Radius + sb.append("|"); + sb.append(circle.getRadius()); + } else { + Rlog.e(TAG, "Unsupported geometry object " + geometry); + return null; + } + return sb.toString(); + } + + /** + * Parse {@link LatLng} from {@link String}. Latitude and longitude are separated by ",". + * Example: "13.56,-55.447". + * + * @param str encoded lat/lng string. + * @Return {@link LatLng} object. + */ + @NonNull + public static LatLng parseLatLngFromString(@NonNull String str) { + String[] latLng = str.split("\\s*,\\s*"); + return new LatLng(Double.parseDouble(latLng[0]), Double.parseDouble(latLng[1])); + } + + /** + * @Return the sign of the given value {@code value} with the specified tolerance. Return 1 + * means the sign is positive, -1 means negative, 0 means the value will be treated as 0. + */ + public static int sign(double value) { + if (value > EPS) return 1; + if (value < -EPS) return -1; + return 0; + } +} diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java index 046bf8c700eb..b9edb9fb1b5c 100644 --- a/telephony/java/com/android/internal/telephony/SmsCbMessage.java +++ b/telephony/java/com/android/internal/telephony/SmsCbMessage.java @@ -16,8 +16,17 @@ package android.telephony; +import android.annotation.Nullable; +import android.content.ContentValues; +import android.database.Cursor; import android.os.Parcel; import android.os.Parcelable; +import android.provider.Telephony.CellBroadcasts; + +import com.android.internal.telephony.CbGeoUtils; +import com.android.internal.telephony.CbGeoUtils.Geometry; + +import java.util.List; /** * Parcelable object containing a received cell broadcast message. There are four different types @@ -138,12 +147,31 @@ public class SmsCbMessage implements Parcelable { /** CMAS warning notification information (CMAS warnings only). */ private final SmsCbCmasInfo mCmasWarningInfo; + /** UNIX timestamp of when the message was received. */ + private final long mReceivedTimeMillis; + + /** CMAS warning area coordinates. */ + private final List<Geometry> mGeometries; + /** * Create a new SmsCbMessage with the specified data. */ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, SmsCbLocation location, int serviceCategory, String language, String body, int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) { + + this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language, + body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */, + System.currentTimeMillis()); + } + + /** + * Create a new {@link SmsCbMessage} with the warning area coordinates information. + */ + public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, + SmsCbLocation location, int serviceCategory, String language, String body, + int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo, + List<Geometry> geometries, long receivedTimeMillis) { mMessageFormat = messageFormat; mGeographicalScope = geographicalScope; mSerialNumber = serialNumber; @@ -154,6 +182,8 @@ public class SmsCbMessage implements Parcelable { mPriority = priority; mEtwsWarningInfo = etwsWarningInfo; mCmasWarningInfo = cmasWarningInfo; + mReceivedTimeMillis = receivedTimeMillis; + mGeometries = geometries; } /** Create a new SmsCbMessage object from a Parcel. */ @@ -184,6 +214,9 @@ public class SmsCbMessage implements Parcelable { mEtwsWarningInfo = null; mCmasWarningInfo = null; } + mReceivedTimeMillis = in.readLong(); + String geoStr = in.readString(); + mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null; } /** @@ -214,6 +247,9 @@ public class SmsCbMessage implements Parcelable { // no ETWS or CMAS warning information dest.writeInt('0'); } + dest.writeLong(mReceivedTimeMillis); + dest.writeString( + mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null); } public static final Parcelable.Creator<SmsCbMessage> CREATOR @@ -293,6 +329,24 @@ public class SmsCbMessage implements Parcelable { } /** + * Get the warning area coordinates information represent by polygons and circles. + * @return a list of geometries, {@link Nullable} means there is no coordinate information + * associated to this message. + */ + @Nullable + public List<Geometry> getGeometries() { + return mGeometries; + } + + /** + * Get the time when this message was received. + * @return the time in millisecond + */ + public long getReceivedTime() { + return mReceivedTimeMillis; + } + + /** * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}). * @return an integer representing 3GPP or 3GPP2 message format */ @@ -368,7 +422,10 @@ public class SmsCbMessage implements Parcelable { + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody + ", priority=" + mPriority + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "") - + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}'; + + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + + ", geo=" + (mGeometries != null + ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null") + + '}'; } /** @@ -379,4 +436,171 @@ public class SmsCbMessage implements Parcelable { public int describeContents() { return 0; } + + /** + * @return the {@link ContentValues} instance that includes the cell broadcast data. + */ + public ContentValues getContentValues() { + ContentValues cv = new ContentValues(16); + cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope); + if (mLocation.getPlmn() != null) { + cv.put(CellBroadcasts.PLMN, mLocation.getPlmn()); + } + if (mLocation.getLac() != -1) { + cv.put(CellBroadcasts.LAC, mLocation.getLac()); + } + if (mLocation.getCid() != -1) { + cv.put(CellBroadcasts.CID, mLocation.getCid()); + } + cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber()); + cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory()); + cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode()); + cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody()); + cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat()); + cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority()); + + SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo(); + if (etwsInfo != null) { + cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType()); + } + + SmsCbCmasInfo cmasInfo = getCmasWarningInfo(); + if (cmasInfo != null) { + cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass()); + cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory()); + cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType()); + cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity()); + cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency()); + cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty()); + } + + cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis); + + if (mGeometries != null) { + cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries)); + } else { + cv.put(CellBroadcasts.GEOMETRIES, (String) null); + } + + return cv; + } + + /** + * Create a {@link SmsCbMessage} instance from a row in the cell broadcast database. + * @param cursor an open SQLite cursor pointing to the row to read + * @return a {@link SmsCbMessage} instance. + * @throws IllegalArgumentException if one of the required columns is missing + */ + public static SmsCbMessage createFromCursor(Cursor cursor) { + int geoScope = cursor.getInt( + cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE)); + int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER)); + int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY)); + String language = cursor.getString( + cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE)); + String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY)); + int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT)); + int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY)); + + String plmn; + int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN); + if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) { + plmn = cursor.getString(plmnColumn); + } else { + plmn = null; + } + + int lac; + int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC); + if (lacColumn != -1 && !cursor.isNull(lacColumn)) { + lac = cursor.getInt(lacColumn); + } else { + lac = -1; + } + + int cid; + int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID); + if (cidColumn != -1 && !cursor.isNull(cidColumn)) { + cid = cursor.getInt(cidColumn); + } else { + cid = -1; + } + + SmsCbLocation location = new SmsCbLocation(plmn, lac, cid); + + SmsCbEtwsInfo etwsInfo; + int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE); + if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) { + int warningType = cursor.getInt(etwsWarningTypeColumn); + etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null); + } else { + etwsInfo = null; + } + + SmsCbCmasInfo cmasInfo = null; + int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS); + if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) { + int messageClass = cursor.getInt(cmasMessageClassColumn); + + int cmasCategory; + int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY); + if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) { + cmasCategory = cursor.getInt(cmasCategoryColumn); + } else { + cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; + } + + int responseType; + int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE); + if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) { + responseType = cursor.getInt(cmasResponseTypeColumn); + } else { + responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; + } + + int severity; + int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY); + if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) { + severity = cursor.getInt(cmasSeverityColumn); + } else { + severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; + } + + int urgency; + int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY); + if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) { + urgency = cursor.getInt(cmasUrgencyColumn); + } else { + urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; + } + + int certainty; + int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY); + if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) { + certainty = cursor.getInt(cmasCertaintyColumn); + } else { + certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; + } + + cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity, + urgency, certainty); + } + + String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES)); + List<Geometry> geometries = + geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null; + + long receivedTimeSec = cursor.getLong( + cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME)); + + return new SmsCbMessage(format, geoScope, serialNum, location, category, + language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec); + } + + /** + * @return {@code True} if this message needs geo-fencing check. + */ + public boolean needGeoFencingCheck() { + return mGeometries != null; + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java index 8015b07fa024..dca4e6b13b90 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java @@ -22,58 +22,36 @@ import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY; import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE; import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.util.Pair; +import android.util.Slog; import com.android.internal.R; +import com.android.internal.telephony.CbGeoUtils; +import com.android.internal.telephony.CbGeoUtils.Circle; +import com.android.internal.telephony.CbGeoUtils.Geometry; +import com.android.internal.telephony.CbGeoUtils.LatLng; +import com.android.internal.telephony.CbGeoUtils.Polygon; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity; +import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme; import java.io.UnsupportedEncodingException; -import java.util.Locale; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; /** * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases. */ public class GsmSmsCbMessage { - - /** - * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. - */ - private static final String[] LANGUAGE_CODES_GROUP_0 = { - Locale.GERMAN.getLanguage(), // German - Locale.ENGLISH.getLanguage(), // English - Locale.ITALIAN.getLanguage(), // Italian - Locale.FRENCH.getLanguage(), // French - new Locale("es").getLanguage(), // Spanish - new Locale("nl").getLanguage(), // Dutch - new Locale("sv").getLanguage(), // Swedish - new Locale("da").getLanguage(), // Danish - new Locale("pt").getLanguage(), // Portuguese - new Locale("fi").getLanguage(), // Finnish - new Locale("nb").getLanguage(), // Norwegian - new Locale("el").getLanguage(), // Greek - new Locale("tr").getLanguage(), // Turkish - new Locale("hu").getLanguage(), // Hungarian - new Locale("pl").getLanguage(), // Polish - null - }; - - /** - * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. - */ - private static final String[] LANGUAGE_CODES_GROUP_2 = { - new Locale("cs").getLanguage(), // Czech - new Locale("he").getLanguage(), // Hebrew - new Locale("ar").getLanguage(), // Arabic - new Locale("ru").getLanguage(), // Russian - new Locale("is").getLanguage(), // Icelandic - null, null, null, null, null, null, null, null, null, null, null - }; + private static final String TAG = GsmSmsCbMessage.class.getSimpleName(); private static final char CARRIAGE_RETURN = 0x0d; @@ -114,8 +92,9 @@ public class GsmSmsCbMessage { * @param pdus PDU bytes */ public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header, - SmsCbLocation location, byte[][] pdus) + SmsCbLocation location, byte[][] pdus) throws IllegalArgumentException { + long receivedTimeMillis = System.currentTimeMillis(); if (header.isEtwsPrimaryNotification()) { // ETSI TS 23.041 ETWS Primary Notification message // ETWS primary message only contains 4 fields including serial number, @@ -125,12 +104,41 @@ public class GsmSmsCbMessage { header.getSerialNumber(), location, header.getServiceCategory(), null, getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()), SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(), - header.getCmasInfo()); + header.getCmasInfo(), null /* geometries */, receivedTimeMillis); + } else if (header.isUmtsFormat()) { + // UMTS format has only 1 PDU + byte[] pdu = pdus[0]; + Pair<String, String> cbData = parseUmtsBody(header, pdu); + String language = cbData.first; + String body = cbData.second; + int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY + : SmsCbMessage.MESSAGE_PRIORITY_NORMAL; + int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; + int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH + + 1 // number of pages + + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data + + // Has Warning Area Coordinates information + List<Geometry> geometries = null; + if (pdu.length > wacDataOffset) { + try { + geometries = parseWarningAreaCoordinates(pdu, wacDataOffset); + } catch (Exception ex) { + // Catch the exception here, the message will be considered as having no WAC + // information which means the message will be broadcasted directly. + Slog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString()); + } + } + + return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, + header.getGeographicalScope(), header.getSerialNumber(), location, + header.getServiceCategory(), language, body, priority, + header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis); } else { String language = null; StringBuilder sb = new StringBuilder(); for (byte[] pdu : pdus) { - Pair<String, String> p = parseBody(header, pdu); + Pair<String, String> p = parseGsmBody(header, pdu); language = p.first; sb.append(p.second); } @@ -140,154 +148,197 @@ public class GsmSmsCbMessage { return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(), header.getSerialNumber(), location, header.getServiceCategory(), language, sb.toString(), priority, - header.getEtwsInfo(), header.getCmasInfo()); + header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */, + receivedTimeMillis); } } /** - * Parse and unpack the body text according to the encoding in the DCS. - * After completing successfully this method will have assigned the body - * text into mBody, and optionally the language code into mLanguage + * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message. * - * @param header the message header to use - * @param pdu the PDU to decode - * @return a Pair of Strings containing the language and body of the message + * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network + * to direct devices to perform a geo-fencing check on selected alerts. + * + * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4 + * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as + * defined in TS 23.041. + * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced + * WEA messages). + * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced + * WEA message. + * @param pdu cell broadcast pdu, including the header + * @return {@link GeoFencingTriggerMessage} instance */ - private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) { - int encoding; - String language = null; - boolean hasLanguageIndicator = false; - int dataCodingScheme = header.getDataCodingScheme(); - - // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, - // section 5. - switch ((dataCodingScheme & 0xf0) >> 4) { - case 0x00: - encoding = SmsConstants.ENCODING_7BIT; - language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f]; - break; - - case 0x01: - hasLanguageIndicator = true; - if ((dataCodingScheme & 0x0f) == 0x01) { - encoding = SmsConstants.ENCODING_16BIT; - } else { - encoding = SmsConstants.ENCODING_7BIT; - } - break; - - case 0x02: - encoding = SmsConstants.ENCODING_7BIT; - language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f]; - break; - - case 0x03: - encoding = SmsConstants.ENCODING_7BIT; - break; + public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) { + try { + // Header length + 1(number of page). ATIS-0700041 define the number of page of + // geo-fencing trigger message is 1. + int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1; + + BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset); + int type = bitReader.read(4); + int length = bitReader.read(7); + // Skip the remained 5 bits + bitReader.skip(); + + int messageIdentifierCount = (length - 2) * 8 / 32; + List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>(); + for (int i = 0; i < messageIdentifierCount; i++) { + // Both messageIdentifier and serialNumber are 16 bits integers. + // ATIS-0700041 Section 5.1.6 + int messageIdentifier = bitReader.read(16); + int serialNumber = bitReader.read(16); + cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber)); + } + return new GeoFencingTriggerMessage(type, cbIdentifiers); + } catch (Exception ex) { + Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString()); + return null; + } + } - case 0x04: - case 0x05: - switch ((dataCodingScheme & 0x0c) >> 2) { - case 0x01: - encoding = SmsConstants.ENCODING_8BIT; - break; - - case 0x02: - encoding = SmsConstants.ENCODING_16BIT; - break; - - case 0x00: - default: - encoding = SmsConstants.ENCODING_7BIT; - break; - } - break; + private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) { + // little-endian + int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset]; + int offset = wacOffset + 2; - case 0x06: - case 0x07: - // Compression not supported - case 0x09: - // UDH structure not supported - case 0x0e: - // Defined by the WAP forum not supported - throw new IllegalArgumentException("Unsupported GSM dataCodingScheme " - + dataCodingScheme); - - case 0x0f: - if (((dataCodingScheme & 0x04) >> 2) == 0x01) { - encoding = SmsConstants.ENCODING_8BIT; - } else { - encoding = SmsConstants.ENCODING_7BIT; - } - break; - - default: - // Reserved values are to be treated as 7-bit - encoding = SmsConstants.ENCODING_7BIT; - break; + if (offset + wacDataLength > pdu.length) { + throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at" + + "least " + offset + wacDataLength + ", actual is " + pdu.length); } - if (header.isUmtsFormat()) { - // Payload may contain multiple pages - int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; - - if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) - * nrPages) { - throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " - + nrPages + " pages"); + BitStreamReader bitReader = new BitStreamReader(pdu, offset); + + List<Geometry> geo = new ArrayList<>(); + int remainedBytes = wacDataLength; + while (remainedBytes > 0) { + int type = bitReader.read(4); + int length = bitReader.read(10); + remainedBytes -= length; + // Skip the 2 remained bits + bitReader.skip(); + + switch (type) { + case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME: + // TODO: handle the maximum wait time in cell broadcast provider. + int maximumWaitTimeSec = bitReader.read(8); + break; + case CbGeoUtils.GEOMETRY_TYPE_POLYGON: + List<LatLng> latLngs = new ArrayList<>(); + // Each coordinate is represented by 44 bits integer. + // ATIS-0700041 5.2.4 Coordinate coding + int n = (length - 2) * 8 / 44; + for (int i = 0; i < n; i++) { + latLngs.add(getLatLng(bitReader)); + } + // Skip the padding bits + bitReader.skip(); + geo.add(new Polygon(latLngs)); + break; + case CbGeoUtils.GEOMETRY_TYPE_CIRCLE: + LatLng center = getLatLng(bitReader); + // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the + // distance unit during geo-fencing. + // ATIS-0700041 5.2.5 radius coding + double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0; + geo.add(new Circle(center, radius)); + break; + default: + throw new IllegalArgumentException("Unsupported geoType = " + type); } + } + return geo; + } - StringBuilder sb = new StringBuilder(); + /** + * The coordinate is (latitude, longitude), represented by a 44 bits integer. + * The coding is defined in ATIS-0700041 5.2.4 + * @param bitReader + * @return coordinate (latitude, longitude) + */ + private static LatLng getLatLng(BitStreamReader bitReader) { + // wacLatitude = floor(((latitude + 90) / 180) * 2^22) + // wacLongitude = floor(((longitude + 180) / 360) * 2^22) + int wacLat = bitReader.read(22); + int wacLng = bitReader.read(22); + + // latitude = wacLatitude * 180 / 2^22 - 90 + // longitude = wacLongitude * 360 / 2^22 -180 + return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180)); + } - for (int i = 0; i < nrPages; i++) { - // Each page is 82 bytes followed by a length octet indicating - // the number of useful octets within those 82 - int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; - int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; + /** + * Parse and unpack the UMTS body text according to the encoding in the data coding scheme. + * + * @param header the message header to use + * @param pdu the PDU to decode + * @return a pair of string containing the language and body of the message in order + */ + private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) { + // Payload may contain multiple pages + int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; + String language = header.getDataCodingSchemeStructedData().language; + + if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) + * nrPages) { + throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " + + nrPages + " pages"); + } - if (length > PDU_BODY_PAGE_LENGTH) { - throw new IllegalArgumentException("Page length " + length - + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); - } + StringBuilder sb = new StringBuilder(); - Pair<String, String> p = unpackBody(pdu, encoding, offset, length, - hasLanguageIndicator, language); - language = p.first; - sb.append(p.second); + for (int i = 0; i < nrPages; i++) { + // Each page is 82 bytes followed by a length octet indicating + // the number of useful octets within those 82 + int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; + int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; + + if (length > PDU_BODY_PAGE_LENGTH) { + throw new IllegalArgumentException("Page length " + length + + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); } - return new Pair<String, String>(language, sb.toString()); - } else { - // Payload is one single page - int offset = SmsCbHeader.PDU_HEADER_LENGTH; - int length = pdu.length - offset; - return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language); + Pair<String, String> p = unpackBody(pdu, offset, length, + header.getDataCodingSchemeStructedData()); + language = p.first; + sb.append(p.second); } + return new Pair(language, sb.toString()); + + } + + /** + * Parse and unpack the GSM body text according to the encoding in the data coding scheme. + * @param header the message header to use + * @param pdu the PDU to decode + * @return a pair of string containing the language and body of the message in order + */ + private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) { + // Payload is one single page + int offset = SmsCbHeader.PDU_HEADER_LENGTH; + int length = pdu.length - offset; + return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData()); } /** - * Unpack body text from the pdu using the given encoding, position and - * length within the pdu + * Unpack body text from the pdu using the given encoding, position and length within the pdu. * * @param pdu The pdu - * @param encoding The encoding, as derived from the DCS * @param offset Position of the first byte to unpack * @param length Number of bytes to unpack - * @param hasLanguageIndicator true if the body text is preceded by a - * language indicator. If so, this method will as a side-effect - * assign the extracted language code into mLanguage - * @param language the language to return if hasLanguageIndicator is false + * @param dcs data coding scheme * @return a Pair of Strings containing the language and body of the message */ - private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length, - boolean hasLanguageIndicator, String language) { + private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length, + DataCodingScheme dcs) { String body = null; - switch (encoding) { + String language = dcs.language; + switch (dcs.encoding) { case SmsConstants.ENCODING_7BIT: body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); - if (hasLanguageIndicator && body != null && body.length() > 2) { + if (dcs.hasLanguageIndicator && body != null && body.length() > 2) { // Language is two GSM characters followed by a CR. // The actual body text is offset by 3 characters. language = body.substring(0, 2); @@ -296,7 +347,7 @@ public class GsmSmsCbMessage { break; case SmsConstants.ENCODING_16BIT: - if (hasLanguageIndicator && pdu.length >= offset + 2) { + if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) { // Language is two GSM characters. // The actual body text is offset by 2 bytes. language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); @@ -330,4 +381,105 @@ public class GsmSmsCbMessage { return new Pair<String, String>(language, body); } + + /** A class use to facilitate the processing of bits stream data. */ + private static final class BitStreamReader { + /** The bits stream represent by a bytes array. */ + private final byte[] mData; + + /** The offset of the current byte. */ + private int mCurrentOffset; + + /** + * The remained bits of the current byte which have not been read. The most significant + * will be read first, so the remained bits are always the least significant bits. + */ + private int mRemainedBit; + + /** + * Constructor + * @param data bit stream data represent by byte array. + * @param offset the offset of the first byte. + */ + BitStreamReader(byte[] data, int offset) { + mData = data; + mCurrentOffset = offset; + mRemainedBit = 8; + } + + /** + * Read the first {@code count} bits. + * @param count the number of bits need to read + * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no + * greater than 32. + */ + public int read(int count) throws IndexOutOfBoundsException { + int val = 0; + while (count > 0) { + if (count >= mRemainedBit) { + val <<= mRemainedBit; + val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1); + count -= mRemainedBit; + mRemainedBit = 8; + ++mCurrentOffset; + } else { + val <<= count; + val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1)) + >> (mRemainedBit - count); + mRemainedBit -= count; + count = 0; + } + } + return val; + } + + /** + * Skip the current bytes if the remained bits is less than 8. This is useful when + * processing the padding or reserved bits. + */ + public void skip() { + if (mRemainedBit < 8) { + mRemainedBit = 8; + ++mCurrentOffset; + } + } + } + + static final class GeoFencingTriggerMessage { + /** + * Indicate the list of active alerts share their warning area coordinates which means the + * broadcast area is the union of the broadcast areas of the active alerts in this list. + */ + public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2; + + public final int type; + public final List<CellBroadcastIdentity> cbIdentifiers; + + GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) { + this.type = type; + this.cbIdentifiers = cbIdentifiers; + } + + boolean shouldShareBroadcastArea() { + return type == TYPE_ACTIVE_ALERT_SHARE_WAC; + } + + static final class CellBroadcastIdentity { + public final int messageIdentifier; + public final int serialNumber; + CellBroadcastIdentity(int messageIdentifier, int serialNumber) { + this.messageIdentifier = messageIdentifier; + this.serialNumber = serialNumber; + } + } + + @Override + public String toString() { + String identifiers = cbIdentifiers.stream() + .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)", + cbIdentifier.messageIdentifier, cbIdentifier.serialNumber)) + .collect(Collectors.joining(",")); + return "triggerType=" + type + " identifiers=" + identifiers; + } + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java index 541ca8d1e5c0..5ad2b9d8f682 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java @@ -215,9 +215,11 @@ public class SmsCbConstants { public static final int MESSAGE_ID_CMAS_ALERT_STATE_LOCAL_TEST_LANGUAGE = 0x112F; // 4399 - /** End of CMAS Message Identifier range (including future extensions). */ - public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER - = 0x112F; // 4399 + /** CMAS Message Identifier for CMAS geo fencing trigger message. */ + public static final int MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER = 0x1130; // 4440 + + /** End of CMAS Message Identifier range. */ + public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER; /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java index 996edfc18eee..6bbff4b91ee7 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -19,9 +19,12 @@ package com.android.internal.telephony.gsm; import android.telephony.SmsCbCmasInfo; import android.telephony.SmsCbEtwsInfo; +import com.android.internal.telephony.SmsConstants; + import dalvik.annotation.compat.UnsupportedAppUsage; import java.util.Arrays; +import java.util.Locale; /** * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by @@ -34,6 +37,39 @@ import java.util.Arrays; * The raw PDU is no longer sent to SMS CB applications. */ public class SmsCbHeader { + /** + * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_0 = { + Locale.GERMAN.getLanguage(), // German + Locale.ENGLISH.getLanguage(), // English + Locale.ITALIAN.getLanguage(), // Italian + Locale.FRENCH.getLanguage(), // French + new Locale("es").getLanguage(), // Spanish + new Locale("nl").getLanguage(), // Dutch + new Locale("sv").getLanguage(), // Swedish + new Locale("da").getLanguage(), // Danish + new Locale("pt").getLanguage(), // Portuguese + new Locale("fi").getLanguage(), // Finnish + new Locale("nb").getLanguage(), // Norwegian + new Locale("el").getLanguage(), // Greek + new Locale("tr").getLanguage(), // Turkish + new Locale("hu").getLanguage(), // Hungarian + new Locale("pl").getLanguage(), // Polish + null + }; + + /** + * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_2 = { + new Locale("cs").getLanguage(), // Czech + new Locale("he").getLanguage(), // Hebrew + new Locale("ar").getLanguage(), // Arabic + new Locale("ru").getLanguage(), // Russian + new Locale("is").getLanguage(), // Icelandic + null, null, null, null, null, null, null, null, null, null, null + }; /** * Length of SMS-CB header @@ -87,6 +123,8 @@ public class SmsCbHeader { private final int mFormat; + private DataCodingScheme mDataCodingSchemeStructedData; + /** ETWS warning notification info. */ private final SmsCbEtwsInfo mEtwsInfo; @@ -166,6 +204,10 @@ public class SmsCbHeader { mNrOfPages = 1; } + if (mDataCodingScheme != -1) { + mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme); + } + if (isEtwsMessage()) { boolean emergencyUserAlert = isEtwsEmergencyUserAlert(); boolean activatePopup = isEtwsPopupAlert(); @@ -206,6 +248,10 @@ public class SmsCbHeader { return mDataCodingScheme; } + DataCodingScheme getDataCodingSchemeStructedData() { + return mDataCodingSchemeStructedData; + } + @UnsupportedAppUsage int getPageIndex() { return mPageIndex; @@ -457,4 +503,93 @@ public class SmsCbHeader { + ", DCS=0x" + Integer.toHexString(mDataCodingScheme) + ", page " + mPageIndex + " of " + mNrOfPages + '}'; } -}
\ No newline at end of file + + /** + * CBS Data Coding Scheme. + * Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme + */ + public static final class DataCodingScheme { + public final int encoding; + public final String language; + public final boolean hasLanguageIndicator; + + public DataCodingScheme(int dataCodingScheme) { + int encoding = 0; + String language = null; + boolean hasLanguageIndicator = false; + + // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, + // section 5. + switch ((dataCodingScheme & 0xf0) >> 4) { + case 0x00: + encoding = SmsConstants.ENCODING_7BIT; + language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f]; + break; + + case 0x01: + hasLanguageIndicator = true; + if ((dataCodingScheme & 0x0f) == 0x01) { + encoding = SmsConstants.ENCODING_16BIT; + } else { + encoding = SmsConstants.ENCODING_7BIT; + } + break; + + case 0x02: + encoding = SmsConstants.ENCODING_7BIT; + language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f]; + break; + + case 0x03: + encoding = SmsConstants.ENCODING_7BIT; + break; + + case 0x04: + case 0x05: + switch ((dataCodingScheme & 0x0c) >> 2) { + case 0x01: + encoding = SmsConstants.ENCODING_8BIT; + break; + + case 0x02: + encoding = SmsConstants.ENCODING_16BIT; + break; + + case 0x00: + default: + encoding = SmsConstants.ENCODING_7BIT; + break; + } + break; + + case 0x06: + case 0x07: + // Compression not supported + case 0x09: + // UDH structure not supported + case 0x0e: + // Defined by the WAP forum not supported + throw new IllegalArgumentException("Unsupported GSM dataCodingScheme " + + dataCodingScheme); + + case 0x0f: + if (((dataCodingScheme & 0x04) >> 2) == 0x01) { + encoding = SmsConstants.ENCODING_8BIT; + } else { + encoding = SmsConstants.ENCODING_7BIT; + } + break; + + default: + // Reserved values are to be treated as 7-bit + encoding = SmsConstants.ENCODING_7BIT; + break; + } + + + this.encoding = encoding; + this.language = language; + this.hasLanguageIndicator = hasLanguageIndicator; + } + } +} diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 0129c4ca5bc1..9a653cf7204c 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -26,7 +26,6 @@ java_sdk_library { ], srcs_lib: "framework-minus-apex", - srcs_lib_whitelist_dirs: ["core/java"], srcs_lib_whitelist_pkgs: ["android"], compile_dex: true, } diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp index 78ebcb97b811..0278b439cfae 100644 --- a/tools/aapt2/optimize/ResourceDeduper.cpp +++ b/tools/aapt2/optimize/ResourceDeduper.cpp @@ -63,13 +63,14 @@ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { // Compare compatible configs for this entry and ensure the values are // equivalent. const ConfigDescription& node_configuration = node_value->config; - for (const auto& sibling : entry_->values) { - if (!sibling->value) { + for (const auto& sibling : parent->children()) { + ResourceConfigValue* sibling_value = sibling->value(); + if (!sibling_value->value) { // Sibling was already removed. continue; } - if (node_configuration.IsCompatibleWith(sibling->config) && - !node_value->value->Equals(sibling->value.get())) { + if (node_configuration.IsCompatibleWith(sibling_value->config) && + !node_value->value->Equals(sibling_value->value.get())) { // The configurations are compatible, but the value is // different, so we can't remove this value. return; diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 2e098aec4f8d..048e318d2802 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -80,11 +80,58 @@ TEST(ResourceDeduperTest, DifferentValuesAreKept) { .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config)); EXPECT_THAT(table, HasValue("android:string/keep", land_config)); } +TEST(ResourceDeduperTest, SameValuesAreDedupedIncompatibleSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is not compatible with ldrtl-night. + const ConfigDescription ldrtl_notnight_config = test::ParseConfigOrDie("ldrtl-notnight"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_notnight_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_notnight_config)); +} + +TEST(ResourceDeduperTest, SameValuesAreDedupedCompatibleNonSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is compatible with ldrtl. + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, land_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", land_config)); +} + TEST(ResourceDeduperTest, LocalesValuesAreKept) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index ca65736d8ec2..e9c24cd6e3e0 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -70,7 +70,6 @@ import java.lang.ref.WeakReference; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -1170,15 +1169,16 @@ public class WifiManager { updateVerboseLoggingEnabledFromService(); } - private IWifiManager getIWifiManager() throws RemoteException { + private IWifiManager getIWifiManager() { if (mService == null) { synchronized (this) { mService = IWifiManager.Stub.asInterface( ServiceManager.getService(Context.WIFI_SERVICE)); - if (mService == null) { - throw new RemoteException("Wifi Service not running"); + if (mService != null) { + updateVerboseLoggingEnabledFromService(); + } else { + Log.e(TAG, "Wifi Service not running yet, ignoring WifiManager API call"); } - updateVerboseLoggingEnabledFromService(); } } return mService; @@ -1223,8 +1223,10 @@ public class WifiManager { @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) public List<WifiConfiguration> getConfiguredNetworks() { try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyList(); ParceledListSlice<WifiConfiguration> parceledList = - getIWifiManager().getConfiguredNetworks(mContext.getOpPackageName()); + iWifiManager.getConfiguredNetworks(mContext.getOpPackageName()); if (parceledList == null) { return Collections.emptyList(); } @@ -1239,8 +1241,10 @@ public class WifiManager { @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL}) public List<WifiConfiguration> getPrivilegedConfiguredNetworks() { try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyList(); ParceledListSlice<WifiConfiguration> parceledList = - getIWifiManager().getPrivilegedConfiguredNetworks(mContext.getOpPackageName()); + iWifiManager.getPrivilegedConfiguredNetworks(mContext.getOpPackageName()); if (parceledList == null) { return Collections.emptyList(); } @@ -1271,14 +1275,16 @@ public class WifiManager { @NonNull List<ScanResult> scanResults) { List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>(); try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyList(); Map<String, Map<Integer, List<ScanResult>>> results = - getIWifiManager().getAllMatchingFqdnsForScanResults( + iWifiManager.getAllMatchingFqdnsForScanResults( scanResults); if (results.isEmpty()) { return configs; } List<WifiConfiguration> wifiConfigurations = - getIWifiManager().getWifiConfigsForPasspointProfiles( + iWifiManager.getWifiConfigsForPasspointProfiles( new ArrayList<>(results.keySet())); for (WifiConfiguration configuration : wifiConfigurations) { Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get( @@ -1313,10 +1319,12 @@ public class WifiManager { public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( @Nullable List<ScanResult> scanResults) { if (scanResults == null) { - return new HashMap<>(); + return Collections.emptyMap(); } try { - return getIWifiManager().getMatchingOsuProviders(scanResults); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyMap(); + return iWifiManager.getMatchingOsuProviders(scanResults); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1343,7 +1351,9 @@ public class WifiManager { public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( @NonNull Set<OsuProvider> osuProviders) { try { - return getIWifiManager().getMatchingPasspointConfigsForOsuProviders( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyMap(); + return iWifiManager.getMatchingPasspointConfigsForOsuProviders( new ArrayList<>(osuProviders)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1428,7 +1438,9 @@ public class WifiManager { */ private int addOrUpdateNetwork(WifiConfiguration config) { try { - return getIWifiManager().addOrUpdateNetwork(config, mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return -1; + return iWifiManager.addOrUpdateNetwork(config, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1654,7 +1666,11 @@ public class WifiManager { Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); Binder binder = new Binder(); try { - getIWifiManager().registerNetworkRequestMatchCallback( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.registerNetworkRequestMatchCallback( binder, new NetworkRequestMatchCallbackProxy(looper, callback), callback.hashCode()); } catch (RemoteException e) { @@ -1680,7 +1696,11 @@ public class WifiManager { Log.v(TAG, "unregisterNetworkRequestMatchCallback: callback=" + callback); try { - getIWifiManager().unregisterNetworkRequestMatchCallback(callback.hashCode()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.unregisterNetworkRequestMatchCallback(callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1714,7 +1734,9 @@ public class WifiManager { public @NetworkSuggestionsStatusCode int addNetworkSuggestions( @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { - return getIWifiManager().addNetworkSuggestions( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; + return iWifiManager.addNetworkSuggestions( networkSuggestions, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1738,7 +1760,9 @@ public class WifiManager { public @NetworkSuggestionsStatusCode int removeNetworkSuggestions( @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { - return getIWifiManager().removeNetworkSuggestions( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL; + return iWifiManager.removeNetworkSuggestions( networkSuggestions, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1754,7 +1778,9 @@ public class WifiManager { @RequiresPermission(ACCESS_WIFI_STATE) public @NonNull List<WifiNetworkSuggestion> getNetworkSuggestions() { try { - return mService.getNetworkSuggestions(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyList(); + return iWifiManager.getNetworkSuggestions(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1784,7 +1810,11 @@ public class WifiManager { */ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { - if (!getIWifiManager().addOrUpdatePasspointConfiguration( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + if (!iWifiManager.addOrUpdatePasspointConfiguration( config, mContext.getOpPackageName())) { throw new IllegalArgumentException(); } @@ -1808,7 +1838,11 @@ public class WifiManager { }) public void removePasspointConfiguration(String fqdn) { try { - if (!getIWifiManager().removePasspointConfiguration( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + if (!iWifiManager.removePasspointConfiguration( fqdn, mContext.getOpPackageName())) { throw new IllegalArgumentException(); } @@ -1832,7 +1866,9 @@ public class WifiManager { }) public List<PasspointConfiguration> getPasspointConfigurations() { try { - return getIWifiManager().getPasspointConfigurations(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) Collections.emptyList(); + return iWifiManager.getPasspointConfigurations(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1852,7 +1888,11 @@ public class WifiManager { */ public void queryPasspointIcon(long bssid, String fileName) { try { - getIWifiManager().queryPasspointIcon(bssid, fileName); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.queryPasspointIcon(bssid, fileName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1866,7 +1906,9 @@ public class WifiManager { */ public int matchProviderWithCurrentNetwork(String fqdn) { try { - return getIWifiManager().matchProviderWithCurrentNetwork(fqdn); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return -1; + return iWifiManager.matchProviderWithCurrentNetwork(fqdn); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1880,7 +1922,11 @@ public class WifiManager { */ public void deauthenticateNetwork(long holdoff, boolean ess) { try { - getIWifiManager().deauthenticateNetwork(holdoff, ess); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.deauthenticateNetwork(holdoff, ess); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1910,7 +1956,9 @@ public class WifiManager { @Deprecated public boolean removeNetwork(int netId) { try { - return getIWifiManager().removeNetwork(netId, mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.removeNetwork(netId, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1965,7 +2013,9 @@ public class WifiManager { boolean success; try { - success = getIWifiManager().enableNetwork( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + success = iWifiManager.enableNetwork( netId, attemptConnect, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2002,7 +2052,9 @@ public class WifiManager { @Deprecated public boolean disableNetwork(int netId) { try { - return getIWifiManager().disableNetwork(netId, mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.disableNetwork(netId, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2025,7 +2077,9 @@ public class WifiManager { @Deprecated public boolean disconnect() { try { - return getIWifiManager().disconnect(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.disconnect(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2049,7 +2103,9 @@ public class WifiManager { @Deprecated public boolean reconnect() { try { - return getIWifiManager().reconnect(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.reconnect(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2073,7 +2129,9 @@ public class WifiManager { @Deprecated public boolean reassociate() { try { - return getIWifiManager().reassociate(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.reassociate(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2159,7 +2217,9 @@ public class WifiManager { private long getSupportedFeatures() { try { - return getIWifiManager().getSupportedFeatures(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return 0L; + return iWifiManager.getSupportedFeatures(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2289,8 +2349,10 @@ public class WifiManager { */ public WifiActivityEnergyInfo getControllerActivityEnergyInfo() { try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; synchronized(this) { - return getIWifiManager().reportActivityInfo(); + return iWifiManager.reportActivityInfo(); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2329,8 +2391,10 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(WorkSource workSource) { try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; String packageName = mContext.getOpPackageName(); - return getIWifiManager().startScan(packageName); + return iWifiManager.startScan(packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2359,7 +2423,9 @@ public class WifiManager { */ public WifiInfo getConnectionInfo() { try { - return getIWifiManager().getConnectionInfo(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getConnectionInfo(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2373,7 +2439,9 @@ public class WifiManager { */ public List<ScanResult> getScanResults() { try { - return getIWifiManager().getScanResults(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return Collections.emptyList(); + return iWifiManager.getScanResults(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2392,7 +2460,9 @@ public class WifiManager { @Deprecated public boolean isScanAlwaysAvailable() { try { - return getIWifiManager().isScanAlwaysAvailable(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.isScanAlwaysAvailable(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2423,7 +2493,11 @@ public class WifiManager { */ public void setCountryCode(@NonNull String country) { try { - getIWifiManager().setCountryCode(country); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.setCountryCode(country); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2438,7 +2512,9 @@ public class WifiManager { @UnsupportedAppUsage public String getCountryCode() { try { - String country = getIWifiManager().getCountryCode(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + String country = iWifiManager.getCountryCode(); return country; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2453,7 +2529,9 @@ public class WifiManager { @UnsupportedAppUsage public boolean isDualBandSupported() { try { - return getIWifiManager().isDualBandSupported(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.isDualBandSupported(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2466,7 +2544,9 @@ public class WifiManager { */ public boolean isDualModeSupported() { try { - return getIWifiManager().needs5GHzToAnyApBandConversion(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.needs5GHzToAnyApBandConversion(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2479,7 +2559,9 @@ public class WifiManager { */ public DhcpInfo getDhcpInfo() { try { - return getIWifiManager().getDhcpInfo(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getDhcpInfo(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2506,7 +2588,9 @@ public class WifiManager { @Deprecated public boolean setWifiEnabled(boolean enabled) { try { - return getIWifiManager().setWifiEnabled(mContext.getOpPackageName(), enabled); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.setWifiEnabled(mContext.getOpPackageName(), enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2521,7 +2605,9 @@ public class WifiManager { */ public int getWifiState() { try { - return getIWifiManager().getWifiEnabledState(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return WIFI_STATE_UNKNOWN; + return iWifiManager.getWifiEnabledState(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2595,7 +2681,11 @@ public class WifiManager { */ public void updateInterfaceIpState(String ifaceName, int mode) { try { - getIWifiManager().updateInterfaceIpState(ifaceName, mode); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.updateInterfaceIpState(ifaceName, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2613,7 +2703,9 @@ public class WifiManager { */ public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) { try { - return getIWifiManager().startSoftAp(wifiConfig); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.startSoftAp(wifiConfig); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2628,7 +2720,9 @@ public class WifiManager { */ public boolean stopSoftAp() { try { - return getIWifiManager().stopSoftAp(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.stopSoftAp(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2696,8 +2790,12 @@ public class WifiManager { LocalOnlyHotspotCallbackProxy proxy = new LocalOnlyHotspotCallbackProxy(this, looper, callback); try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } String packageName = mContext.getOpPackageName(); - int returnCode = getIWifiManager().startLocalOnlyHotspot( + int returnCode = iWifiManager.startLocalOnlyHotspot( proxy.getMessenger(), new Binder(), packageName); if (returnCode != LocalOnlyHotspotCallback.REQUEST_REGISTERED) { // Send message to the proxy to make sure we call back on the correct thread @@ -2749,7 +2847,11 @@ public class WifiManager { } mLOHSCallbackProxy = null; try { - getIWifiManager().stopLocalOnlyHotspot(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.stopLocalOnlyHotspot(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2779,7 +2881,11 @@ public class WifiManager { Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); mLOHSObserverProxy = new LocalOnlyHotspotObserverProxy(this, looper, observer); try { - getIWifiManager().startWatchLocalOnlyHotspot( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.startWatchLocalOnlyHotspot( mLOHSObserverProxy.getMessenger(), new Binder()); mLOHSObserverProxy.registered(); } catch (RemoteException e) { @@ -2803,7 +2909,11 @@ public class WifiManager { } mLOHSObserverProxy = null; try { - getIWifiManager().stopWatchLocalOnlyHotspot(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.stopWatchLocalOnlyHotspot(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2823,7 +2933,9 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState() { try { - return getIWifiManager().getWifiApEnabledState(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return WIFI_AP_STATE_FAILED; + return iWifiManager.getWifiApEnabledState(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2852,7 +2964,9 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public WifiConfiguration getWifiApConfiguration() { try { - return getIWifiManager().getWifiApConfiguration(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getWifiApConfiguration(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2869,7 +2983,9 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) { try { - return getIWifiManager().setWifiApConfiguration( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.setWifiApConfiguration( wifiConfig, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2885,7 +3001,11 @@ public class WifiManager { public void notifyUserOfApBandConversion() { Log.d(TAG, "apBand was converted, notify the user"); try { - getIWifiManager().notifyUserOfApBandConversion(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.notifyUserOfApBandConversion(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2913,7 +3033,11 @@ public class WifiManager { */ public void setTdlsEnabled(InetAddress remoteIPAddress, boolean enable) { try { - getIWifiManager().enableTdls(remoteIPAddress.getHostAddress(), enable); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.enableTdls(remoteIPAddress.getHostAddress(), enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2927,7 +3051,11 @@ public class WifiManager { */ public void setTdlsEnabledWithMacAddress(String remoteMacAddress, boolean enable) { try { - getIWifiManager().enableTdlsWithMacAddress(remoteMacAddress, enable); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.enableTdlsWithMacAddress(remoteMacAddress, enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3216,7 +3344,11 @@ public class WifiManager { Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); Binder binder = new Binder(); try { - getIWifiManager().registerSoftApCallback( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.registerSoftApCallback( binder, new SoftApCallbackProxy(looper, callback), callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -3237,7 +3369,11 @@ public class WifiManager { Log.v(TAG, "unregisterSoftApCallback: callback=" + callback); try { - getIWifiManager().unregisterSoftApCallback(callback.hashCode()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.unregisterSoftApCallback(callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3807,7 +3943,11 @@ public class WifiManager { public void disableEphemeralNetwork(String SSID) { if (SSID == null) throw new IllegalArgumentException("SSID cannot be null"); try { - getIWifiManager().disableEphemeralNetwork(SSID, mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.disableEphemeralNetwork(SSID, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3851,7 +3991,9 @@ public class WifiManager { @UnsupportedAppUsage private Messenger getWifiServiceMessenger() { try { - return getIWifiManager().getWifiServiceMessenger(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getWifiServiceMessenger(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3911,10 +4053,14 @@ public class WifiManager { synchronized (mBinder) { if (mRefCounted ? (++mRefCount == 1) : (!mHeld)) { try { - getIWifiManager().acquireWifiLock(mBinder, mLockType, mTag, mWorkSource); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource); synchronized (WifiManager.this) { if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { - getIWifiManager().releaseWifiLock(mBinder); + iWifiManager.releaseWifiLock(mBinder); throw new UnsupportedOperationException( "Exceeded maximum number of wifi locks"); } @@ -3944,7 +4090,11 @@ public class WifiManager { synchronized (mBinder) { if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { try { - getIWifiManager().releaseWifiLock(mBinder); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.releaseWifiLock(mBinder); synchronized (WifiManager.this) { mActiveLockCount--; } @@ -4007,7 +4157,11 @@ public class WifiManager { } if (changed && mHeld) { try { - getIWifiManager().updateWifiLockWorkSource(mBinder, mWorkSource); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.updateWifiLockWorkSource(mBinder, mWorkSource); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4035,7 +4189,11 @@ public class WifiManager { synchronized (mBinder) { if (mHeld) { try { - getIWifiManager().releaseWifiLock(mBinder); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.releaseWifiLock(mBinder); synchronized (WifiManager.this) { mActiveLockCount--; } @@ -4148,10 +4306,14 @@ public class WifiManager { synchronized (mBinder) { if (mRefCounted ? (++mRefCount == 1) : (!mHeld)) { try { - getIWifiManager().acquireMulticastLock(mBinder, mTag); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.acquireMulticastLock(mBinder, mTag); synchronized (WifiManager.this) { if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { - getIWifiManager().releaseMulticastLock(mTag); + iWifiManager.releaseMulticastLock(mTag); throw new UnsupportedOperationException( "Exceeded maximum number of wifi locks"); } @@ -4193,7 +4355,11 @@ public class WifiManager { synchronized (mBinder) { if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { try { - getIWifiManager().releaseMulticastLock(mTag); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.releaseMulticastLock(mTag); synchronized (WifiManager.this) { mActiveLockCount--; } @@ -4270,7 +4436,9 @@ public class WifiManager { */ public boolean isMulticastEnabled() { try { - return getIWifiManager().isMulticastEnabled(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + return iWifiManager.isMulticastEnabled(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4283,7 +4451,9 @@ public class WifiManager { @UnsupportedAppUsage public boolean initializeMulticastFiltering() { try { - getIWifiManager().initializeMulticastFiltering(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return false; + iWifiManager.initializeMulticastFiltering(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -4308,7 +4478,11 @@ public class WifiManager { @UnsupportedAppUsage public void enableVerboseLogging (int verbose) { try { - getIWifiManager().enableVerboseLogging(verbose); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.enableVerboseLogging(verbose); } catch (Exception e) { //ignore any failure here Log.e(TAG, "enableVerboseLogging " + e.toString()); @@ -4323,7 +4497,9 @@ public class WifiManager { @UnsupportedAppUsage public int getVerboseLoggingLevel() { try { - return getIWifiManager().getVerboseLoggingLevel(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return -1; + return iWifiManager.getVerboseLoggingLevel(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4336,7 +4512,11 @@ public class WifiManager { */ public void factoryReset() { try { - getIWifiManager().factoryReset(mContext.getOpPackageName()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.factoryReset(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4350,7 +4530,9 @@ public class WifiManager { @UnsupportedAppUsage public Network getCurrentNetwork() { try { - return getIWifiManager().getCurrentNetwork(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getCurrentNetwork(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4382,7 +4564,11 @@ public class WifiManager { */ public void enableWifiConnectivityManager(boolean enabled) { try { - getIWifiManager().enableWifiConnectivityManager(enabled); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.enableWifiConnectivityManager(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4394,7 +4580,9 @@ public class WifiManager { */ public byte[] retrieveBackupData() { try { - return getIWifiManager().retrieveBackupData(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.retrieveBackupData(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4406,7 +4594,11 @@ public class WifiManager { */ public void restoreBackupData(byte[] data) { try { - getIWifiManager().restoreBackupData(data); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.restoreBackupData(data); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4422,7 +4614,11 @@ public class WifiManager { @Deprecated public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) { try { - getIWifiManager().restoreSupplicantBackupData(supplicantData, ipConfigData); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.restoreSupplicantBackupData(supplicantData, ipConfigData); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4451,7 +4647,11 @@ public class WifiManager { throw new IllegalArgumentException("callback must not be null"); } try { - getIWifiManager().startSubscriptionProvisioning(provider, + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.startSubscriptionProvisioning(provider, new ProvisioningCallbackProxy(executor, callback)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -4565,7 +4765,11 @@ public class WifiManager { Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); Binder binder = new Binder(); try { - getIWifiManager().registerTrafficStateCallback( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.registerTrafficStateCallback( binder, new TrafficStateCallbackProxy(looper, callback), callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -4585,7 +4789,11 @@ public class WifiManager { Log.v(TAG, "unregisterTrafficStateCallback: callback=" + callback); try { - getIWifiManager().unregisterTrafficStateCallback(callback.hashCode()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.unregisterTrafficStateCallback(callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4641,7 +4849,9 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses() { try { - return getIWifiManager().getFactoryMacAddresses(); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) return null; + return iWifiManager.getFactoryMacAddresses(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4710,7 +4920,11 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(@DeviceMobilityState int state) { try { - getIWifiManager().setDeviceMobilityState(state); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.setDeviceMobilityState(state); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4765,7 +4979,11 @@ public class WifiManager { @NonNull EasyConnectStatusCallback callback) { Binder binder = new Binder(); try { - getIWifiManager().startDppAsConfiguratorInitiator( + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.startDppAsConfiguratorInitiator( binder, enrolleeUri, selectedNetworkId, enrolleeNetworkRole, new EasyConnectCallbackProxy(executor, callback)); } catch (RemoteException e) { @@ -4792,7 +5010,11 @@ public class WifiManager { @NonNull EasyConnectStatusCallback callback) { Binder binder = new Binder(); try { - getIWifiManager().startDppAsEnrolleeInitiator(binder, configuratorUri, + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.startDppAsEnrolleeInitiator(binder, configuratorUri, new EasyConnectCallbackProxy(executor, callback)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -4813,8 +5035,12 @@ public class WifiManager { android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession() { try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } /* Request lower layers to stop/abort and clear resources */ - getIWifiManager().stopDppSession(); + iWifiManager.stopDppSession(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4913,7 +5139,11 @@ public class WifiManager { Log.v(TAG, "addOnWifiUsabilityStatsListener: listener=" + listener); } try { - getIWifiManager().addOnWifiUsabilityStatsListener(new Binder(), + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.addOnWifiUsabilityStatsListener(new Binder(), new IOnWifiUsabilityStatsListener.Stub() { @Override public void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq, @@ -4950,7 +5180,11 @@ public class WifiManager { Log.v(TAG, "removeOnWifiUsabilityStatsListener: listener=" + listener); } try { - getIWifiManager().removeOnWifiUsabilityStatsListener(listener.hashCode()); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.removeOnWifiUsabilityStatsListener(listener.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4973,7 +5207,11 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { try { - getIWifiManager().updateWifiUsabilityScore(seqNum, score, predictionHorizonSec); + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } |