diff options
718 files changed, 17198 insertions, 7921 deletions
diff --git a/Android.bp b/Android.bp index 874d76fe8d00..03a6af5997bb 100644 --- a/Android.bp +++ b/Android.bp @@ -339,9 +339,7 @@ java_defaults { "sax/java", "telecomm/java", - // TODO(b/148660295): remove this - "apex/media/framework/java", - + "apex/media/aidl/stable", // TODO(b/147699819): remove this "telephony/java", ], @@ -911,6 +909,7 @@ cc_library { filegroup { name: "incremental_aidl", srcs: [ + "core/java/android/os/incremental/IIncrementalServiceConnector.aidl", "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl", ], path: "core/java", diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 9abb308534df..2fd2e33bbc37 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -9,6 +9,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp core/jni/ libs/input/ services/core/jni/ + services/incremental/ [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java index 73b4a1914ad1..836e6b617395 100644 --- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java @@ -192,6 +192,11 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { Assume.assumeNoException( new AssertionError("onAnimationCanceled should not be called")); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + /* no-op */ + } }; recentsSemaphore.tryAcquire(); diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index 8633c9613138..8139a2e963c5 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -30,6 +30,7 @@ import android.util.MergedConfiguration; import android.view.DisplayCutout; import android.view.IWindow; import android.view.IWindowSession; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.View; @@ -120,6 +121,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; final IWindow mWindow; final View mView; final WindowManager.LayoutParams mParams; @@ -152,7 +154,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, mOutSurfaceSize, + mOutSurfaceControl, mOutInsetsState, mOutControls, mOutSurfaceSize, mOutBlastSurfaceControl); } } diff --git a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java index 4ac3adfd19ce..c72cc9d635e0 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java @@ -29,6 +29,7 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.IWindowSession; import android.view.InputChannel; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.View; import android.view.WindowManager; @@ -91,6 +92,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase final DisplayCutout.ParcelableWrapper mOutDisplayCutout = new DisplayCutout.ParcelableWrapper(); final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; TestWindow() { mLayoutParams.setTitle(TestWindow.class.getName()); @@ -109,7 +111,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase long startTime = SystemClock.elapsedRealtimeNanos(); session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets, - mOutDisplayCutout, inputChannel, mOutInsetsState); + mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls); final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime; state.addExtraResult("add", elapsedTimeNsOfAdd); diff --git a/apex/Android.bp b/apex/Android.bp index 5f418d47f090..67cd0d7fcd1e 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -67,7 +67,7 @@ stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_framework_stubs_args, installable: false, - sdk_version: "current", + sdk_version: "module_current", filter_packages: framework_packages_to_document, check_api: { current: { @@ -86,7 +86,7 @@ stubs_defaults { args: mainline_framework_stubs_args + priv_apps, libs: ["framework-annotations-lib"], installable: false, - sdk_version: "system_current", + sdk_version: "module_current", filter_packages: framework_packages_to_document, check_api: { current: { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index b6f85b2d06bf..1ab79380fb0d 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1364,6 +1364,19 @@ public class AppStandbyController implements AppStandbyInternal { if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } + } else if (newBucket == STANDBY_BUCKET_RARE + && getBucketForLocked(packageName, userId, elapsedRealtime) + == STANDBY_BUCKET_RESTRICTED) { + // Prediction doesn't think the app will be used anytime soon and + // it's been long enough that it could just time out into restricted, + // so time it out there instead. Using TIMEOUT will allow prediction + // to raise the bucket when it needs to. + newBucket = STANDBY_BUCKET_RESTRICTED; + reason = REASON_MAIN_TIMEOUT; + if (DEBUG) { + Slog.d(TAG, + "Prediction to RARE overridden by timeout into RESTRICTED"); + } } } diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp new file mode 100644 index 000000000000..409a04897f56 --- /dev/null +++ b/apex/media/aidl/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +filegroup { + name: "stable-mediasession2-aidl-srcs", + srcs: ["stable/**/*.aidl"], + path: "stable", +} + +filegroup { + name: "private-mediasession2-aidl-srcs", + srcs: ["private/**/I*.aidl"], + path: "private", +} + +filegroup { + name: "mediasession2-aidl-srcs", + srcs: [ + ":private-mediasession2-aidl-srcs", + ":stable-mediasession2-aidl-srcs", + ], +} diff --git a/apex/media/framework/java/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl index 64edafcb11fc..64edafcb11fc 100644 --- a/apex/media/framework/java/android/media/Controller2Link.aidl +++ b/apex/media/aidl/private/android/media/Controller2Link.aidl diff --git a/apex/media/framework/java/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl index 42c6e70529ec..42c6e70529ec 100644 --- a/apex/media/framework/java/android/media/IMediaController2.aidl +++ b/apex/media/aidl/private/android/media/IMediaController2.aidl diff --git a/apex/media/framework/java/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl index 26e717b39afc..26e717b39afc 100644 --- a/apex/media/framework/java/android/media/IMediaSession2.aidl +++ b/apex/media/aidl/private/android/media/IMediaSession2.aidl diff --git a/apex/media/framework/java/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl index 10ac1be0a36e..10ac1be0a36e 100644 --- a/apex/media/framework/java/android/media/IMediaSession2Service.aidl +++ b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl diff --git a/apex/media/framework/java/android/media/Session2Command.aidl b/apex/media/aidl/private/android/media/Session2Command.aidl index 43a7b123ed29..43a7b123ed29 100644 --- a/apex/media/framework/java/android/media/Session2Command.aidl +++ b/apex/media/aidl/private/android/media/Session2Command.aidl diff --git a/apex/media/framework/java/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl index c5980e9e77fd..c5980e9e77fd 100644 --- a/apex/media/framework/java/android/media/Session2Token.aidl +++ b/apex/media/aidl/stable/android/media/Session2Token.aidl diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 99e82e7a3367..34fe22879ca9 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -55,17 +55,15 @@ filegroup { name: "updatable-media-srcs", srcs: [ ":mediaparser-srcs", - ":mediasession2-srcs", + ":mediasession2-java-srcs", + ":mediasession2-aidl-srcs", ], } filegroup { - name: "mediasession2-srcs", + name: "mediasession2-java-srcs", srcs: [ "java/android/media/Controller2Link.java", - "java/android/media/IMediaController2.aidl", - "java/android/media/IMediaSession2.aidl", - "java/android/media/IMediaSession2Service.aidl", "java/android/media/MediaConstants.java", "java/android/media/MediaController2.java", "java/android/media/MediaSession2.java", @@ -83,7 +81,7 @@ filegroup { srcs: [ "java/android/media/MediaParser.java" ], - path: "java" + path: "java", } stubs_defaults { @@ -94,7 +92,6 @@ stubs_defaults { // TODO(b/135922046) remove this include_dirs: ["frameworks/base/core/java"], }, - sdk_version: "system_current", } droidstubs { diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 793247e88614..fc9052e8beac 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -46,12 +46,10 @@ stubs_defaults { name: "framework-permission-stubs-defaults", srcs: [ ":framework-permission-sources" ], libs: [ "framework-annotations-lib" ], - sdk_version: "system_current", } droidstubs { name: "framework-permission-stubs-srcs-publicapi", - sdk_version: "system_current", defaults: [ "framework-module-stubs-defaults-publicapi", "framework-permission-stubs-defaults", @@ -60,7 +58,6 @@ droidstubs { droidstubs { name: "framework-permission-stubs-srcs-systemapi", - sdk_version: "system_current", defaults: [ "framework-module-stubs-defaults-systemapi", "framework-permission-stubs-defaults", @@ -69,7 +66,6 @@ droidstubs { droidstubs { name: "framework-permission-api-module_libs_api", - sdk_version: "system_current", defaults: [ "framework-module-api-defaults-module_libs_api", "framework-permission-stubs-defaults", @@ -78,7 +74,6 @@ droidstubs { droidstubs { name: "framework-permission-stubs-srcs-module_libs_api", - sdk_version: "system_current", defaults: [ "framework-module-stubs-defaults-module_libs_api", "framework-permission-stubs-defaults", diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 5cdcdd335a12..2eb7e99a9dab 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -48,5 +48,5 @@ java_library { name: "service-permission-stubs", srcs: [":service-permission-stubs-srcs"], defaults: ["service-module-stubs-defaults"], - visibility: ["//visibility:private"] + visibility: ["//frameworks/base/services/core"] } diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 707113b9672c..3eabb88fec7d 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -48,7 +48,6 @@ stubs_defaults { name: "framework-sdkextensions-stubs-defaults", srcs: [ ":framework-sdkextensions-sources" ], libs: [ "framework-annotations-lib" ], - sdk_version: "system_current", } droidstubs { diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp index 32e13e31eebe..15d74951019d 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -67,7 +67,6 @@ cc_library_shared { "liblog", // Has a stable abi - should not be copied into apex. "libstatssocket", ], - //TODO: is libc++_static correct? stl: "libc++_static", cflags: [ "-Wall", diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index b94928f09ae0..5cdb3249501b 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -59,9 +59,6 @@ interface IStatsCompanionService { /** Cancel any alarm for the purpose of subscriber triggering. */ oneway void cancelAlarmForSubscriberTriggering(); - /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ - oneway void triggerUidSnapshot(); - /** * Ask StatsCompanionService if the given permission is allowed for a particular process * and user ID. statsd is incapable of doing this check itself because checkCallingPermission diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index aad51124c8d2..66e41cca96a7 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -153,7 +153,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private static void informAllUidsLocked(Context context) throws RemoteException { + private static void informAllUids(Context context) { UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); PackageManager pm = context.getPackageManager(); final List<UserHandle> users = um.getUserHandles(true); @@ -168,18 +168,26 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Log.e(TAG, "Failed to create a pipe to send uid map data.", e); return; } - sStatsd.informAllUidData(fds[0]); - try { - fds[0].close(); - } catch (IOException e) { - Log.e(TAG, "Failed to close the read side of the pipe.", e); - } - final ParcelFileDescriptor writeFd = fds[1]; HandlerThread backgroundThread = new HandlerThread( "statsCompanionService.bg", THREAD_PRIORITY_BACKGROUND); backgroundThread.start(); Handler handler = new Handler(backgroundThread.getLooper()); handler.post(() -> { + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + try { + statsd.informAllUidData(fds[0]); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send uid map to statsd"); + } + try { + fds[0].close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close the read side of the pipe.", e); + } + final ParcelFileDescriptor writeFd = fds[1]; FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd); try { ProtoOutputStream output = new ProtoOutputStream(fout); @@ -188,7 +196,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { for (UserHandle userHandle : users) { List<PackageInfo> pi = pm.getInstalledPackagesAsUser(PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_ANY_USER, + | PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_APEX, userHandle.getIdentifier()); for (int j = 0; j < pi.size(); j++) { if (pi.get(j).applicationInfo != null) { @@ -319,19 +328,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private static final class UserUpdateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - synchronized (sStatsdLock) { - if (sStatsd == null) { - Log.w(TAG, "Could not access statsd for UserUpdateReceiver"); - return; - } - try { - // Pull the latest state of UID->app name, version mapping. - // Needed since the new user basically has a version of every app. - informAllUidsLocked(context); - } catch (RemoteException e) { - Log.e(TAG, "Failed to inform statsd latest update of all apps", e); - } - } + // Pull the latest state of UID->app name, version mapping. + // Needed since the new user basically has a version of every app. + informAllUids(context); } } @@ -589,21 +588,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } @Override // Binder call - public void triggerUidSnapshot() { - StatsCompanion.enforceStatsdCallingUid(); - synchronized (sStatsdLock) { - final long token = Binder.clearCallingIdentity(); - try { - informAllUidsLocked(mContext); - } catch (RemoteException e) { - Log.e(TAG, "Failed to trigger uid snapshot.", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @Override // Binder call public boolean checkPermission(String permission, int pid, int uid) { StatsCompanion.enforceStatsdCallingUid(); return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; @@ -707,7 +691,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { try { // Pull the latest state of UID->app name, version mapping when // statsd starts. - informAllUidsLocked(mContext); + informAllUids(mContext); } finally { Binder.restoreCallingIdentity(token); } diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp index 2d64f190839c..0df96e149d4f 100644 --- a/apex/statsd/tests/libstatspull/Android.bp +++ b/apex/statsd/tests/libstatspull/Android.bp @@ -32,7 +32,7 @@ android_test { "protos/**/*.proto", ], test_suites: [ - "general-tests", + "device-tests", ], platform_apis: true, privileged: true, diff --git a/api/current.txt b/api/current.txt index b4db1f7bc10a..80e2d00b3e48 100644 --- a/api/current.txt +++ b/api/current.txt @@ -46230,7 +46230,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); method public String getDefaultDialerPackage(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1Number(android.telecom.PhoneAccountHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); diff --git a/api/test-current.txt b/api/test-current.txt index 4eeaaf87ea0d..641767c7bccd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -214,6 +214,7 @@ package android.app { field public static final String OPSTR_GPS = "android:gps"; field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground"; field public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage"; + field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage"; field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels"; field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone"; field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells"; @@ -1504,6 +1505,13 @@ package android.media { ctor public AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String); } + public class AudioSystem { + method public static float getMasterBalance(); + method public static final int getNumStreamTypes(); + method public static int setMasterBalance(float); + field public static final int STREAM_DEFAULT = -1; // 0xffffffff + } + public static final class AudioTrack.MetricsConstants { field public static final String ATTRIBUTES = "android.media.audiotrack.attributes"; field public static final String CHANNEL_MASK = "android.media.audiotrack.channelMask"; @@ -3522,6 +3530,32 @@ package android.service.textclassifier { } +package android.service.watchdog { + + public abstract class ExplicitHealthCheckService extends android.app.Service { + ctor public ExplicitHealthCheckService(); + method public final void notifyHealthCheckPassed(@NonNull String); + method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onCancelHealthCheck(@NonNull String); + method @NonNull public abstract java.util.List<java.lang.String> onGetRequestedPackages(); + method @NonNull public abstract java.util.List<android.service.watchdog.ExplicitHealthCheckService.PackageConfig> onGetSupportedPackages(); + method public abstract void onRequestHealthCheck(@NonNull String); + method public void setCallback(@Nullable android.os.RemoteCallback); + field public static final String BIND_PERMISSION = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"; + field public static final String SERVICE_INTERFACE = "android.service.watchdog.ExplicitHealthCheckService"; + } + + public static final class ExplicitHealthCheckService.PackageConfig implements android.os.Parcelable { + ctor public ExplicitHealthCheckService.PackageConfig(@NonNull String, long); + method public int describeContents(); + method public long getHealthCheckTimeoutMillis(); + method @NonNull public String getPackageName(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.watchdog.ExplicitHealthCheckService.PackageConfig> CREATOR; + } + +} + package android.telecom { public final class Call { @@ -4681,7 +4715,6 @@ package android.util { field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; field public static final String FFLAG_PREFIX = "sys.fflag."; field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; - field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req"; field public static final String PERSIST_PREFIX = "persist.sys.fflag.override."; field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; @@ -4782,7 +4815,14 @@ package android.view { public final class Display { method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); + method public int getType(); method public boolean hasAccess(int); + field public static final int TYPE_EXTERNAL = 2; // 0x2 + field public static final int TYPE_INTERNAL = 1; // 0x1 + field public static final int TYPE_OVERLAY = 4; // 0x4 + field public static final int TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_VIRTUAL = 5; // 0x5 + field public static final int TYPE_WIFI = 3; // 0x3 } public class FocusFinder { @@ -4820,6 +4860,13 @@ package android.view { method public abstract String asyncImpl() default ""; } + public final class SurfaceControl implements android.os.Parcelable { + ctor public SurfaceControl(@NonNull android.view.SurfaceControl); + method public static long acquireFrameRateFlexibilityToken(); + method public boolean isSameSurface(@NonNull android.view.SurfaceControl); + method public static void releaseFrameRateFlexibilityToken(long); + } + public class SurfaceControlViewHost { method public void relayout(android.view.WindowManager.LayoutParams); method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); @@ -5169,10 +5216,10 @@ package android.window { method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken); method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int); + field public static final int FEATURE_DEFAULT_TASK_CONTAINER = 1; // 0x1 field public static final int FEATURE_ROOT = 0; // 0x0 field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0 field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710 - field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1 field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711 field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2 diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING index 26ccf038cba2..9e0fb84c7949 100644 --- a/cmds/idmap2/TEST_MAPPING +++ b/cmds/idmap2/TEST_MAPPING @@ -3,5 +3,10 @@ { "name" : "idmap2_tests" } + ], + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/server/om" + } ] } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index e55ea6c00545..75fc7f714ce3 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -149,15 +149,21 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, return error(idmap.GetErrorMessage()); } + // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees + // that existing memory maps will continue to be valid and unaffected. + unlink(idmap_path.c_str()); + umask(kIdmapFilePermissionMask); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); } + BinaryStreamVisitor visitor(fout); (*idmap)->accept(&visitor); fout.close(); if (fout.fail()) { + unlink(idmap_path.c_str()); return error("failed to write to idmap path " + idmap_path); } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index d3d7e1d483e8..65061d0c9bda 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -113,17 +113,18 @@ cc_defaults { static_libs: [ "libbase", "libcutils", + "libgtest_prod", "libprotoutil", "libstatsmetadata", "libstatslog_statsd", "libsysutils", "libutils", + "statsd-aidl-ndk_platform", ], shared_libs: [ "libbinder_ndk", "libincident", "liblog", - "statsd-aidl-ndk_platform", ], } @@ -268,10 +269,11 @@ cc_binary { proto: { type: "lite", + static: true, }, + stl: "libc++_static", shared_libs: [ - "libgtest_prod", "libstatssocket", ], diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp index 28bf21ae52bf..743ccc4ed451 100644 --- a/cmds/statsd/benchmark/filter_value_benchmark.cpp +++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ #include <vector> -#include "benchmark/benchmark.h" + #include "FieldValue.h" #include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" #include "logd/LogEvent.h" -#include "stats_log_util.h" +#include "metric_util.h" #include "stats_event.h" +#include "stats_log_util.h" namespace android { namespace os { @@ -34,24 +36,13 @@ static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matche std::vector<int> attributionUids = {100, 100}; std::vector<string> attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); AStatsEvent_writeFloat(statsEvent, 3.2f); AStatsEvent_writeString(statsEvent, "LOCATION"); AStatsEvent_writeInt64(statsEvent, 990); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - event->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, event); field_matcher->set_field(1); auto child = field_matcher->add_child(); diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp index c7d01cc406fc..7a455650a31b 100644 --- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp +++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ #include <vector> -#include "benchmark/benchmark.h" + #include "FieldValue.h" #include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" #include "logd/LogEvent.h" -#include "stats_log_util.h" +#include "metric_util.h" #include "stats_event.h" +#include "stats_log_util.h" namespace android { namespace os { @@ -34,24 +36,13 @@ static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { std::vector<int> attributionUids = {100, 100}; std::vector<string> attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); AStatsEvent_writeFloat(statsEvent, 3.2f); AStatsEvent_writeString(statsEvent, "LOCATION"); AStatsEvent_writeInt64(statsEvent, 990); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - event->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, event); link->conditionId = 1; diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index 482d66fc7556..89fd3d9b29ab 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -247,21 +247,37 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags) { + vector<const char*> cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } + + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast<const uint32_t*>(attributionUids.data()), + cTags.data(), attributionUids.size()); +} + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + logEvent->parseBuffer(buf, size); + + AStatsEvent_release(statsEvent); +} + std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( uint64_t timestampNs, const android::view::DisplayStateEnum state) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -272,24 +288,12 @@ std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, jobName.c_str()); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -319,24 +323,12 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, name.c_str()); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h index c5fcf7c27440..3efaa850a921 100644 --- a/cmds/statsd/benchmark/metric_util.h +++ b/cmds/statsd/benchmark/metric_util.h @@ -18,6 +18,7 @@ #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "src/StatsLogProcessor.h" #include "src/logd/LogEvent.h" +#include "stats_event.h" #include "statslog.h" namespace android { @@ -92,6 +93,11 @@ FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, FieldMatcher CreateAttributionUidDimensions(const int atomId, const std::vector<Position>& positions); +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags); + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + // Create log event for screen state changed. std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( uint64_t timestampNs, const android::view::DisplayStateEnum state); diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp index 4385964f7f0e..cfc1de49e259 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -120,9 +120,9 @@ bool isAttributionUidField(const FieldValue& value) { } int32_t getUidIfExists(const FieldValue& value) { - // the field is uid field if the field is the uid field in attribution node or marked as - // is_uid in atoms.proto - bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue); + // the field is uid field if the field is the uid field in attribution node + // or annotated as such in the atom + bool isUid = isAttributionUidField(value) || isUidField(value); return isUid ? value.mValue.int_value : -1; } @@ -134,16 +134,8 @@ bool isAttributionUidField(const Field& field, const Value& value) { return false; } -bool isUidField(const Field& field, const Value& value) { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag()); - - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto - return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField && - value.getType() == INT; - } - - return false; +bool isUidField(const FieldValue& fieldValue) { + return fieldValue.mAnnotations.isUidField(); } Value::Value(const Value& from) { diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 3536e5a5c962..92e09ea0f8f9 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -367,7 +367,8 @@ public: enum { NESTED_POS = 0x0, PRIMARY_POS = 0x1, - EXCLUSIVE_POS = 0x2 + EXCLUSIVE_POS = 0x2, + UID_POS = 0x3 }; inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); } @@ -376,6 +377,8 @@ public: inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); } + inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } + inline void setResetState(int resetState) { mResetState = resetState; } // Default value = false @@ -387,6 +390,9 @@ public: // Default value = false inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); } + // Default value = false + inline bool isUidField() const { return getValueFromBitmask(UID_POS); } + // If a reset state is not sent in the StatsEvent, returns -1. Note that a // reset satate is only sent if and only if a reset should be triggered. inline int getResetState() const { return mResetState; } @@ -402,7 +408,7 @@ private: } // This is a bitmask over all annotations stored in boolean form. Because - // there are only 3 booleans, just one byte is required. + // there are only 4 booleans, just one byte is required. uint8_t mBooleanBitmask = 0; int mResetState = -1; @@ -449,7 +455,7 @@ int getUidIfExists(const FieldValue& value); void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output); bool isAttributionUidField(const Field& field, const Value& value); -bool isUidField(const Field& field, const Value& value); +bool isUidField(const FieldValue& fieldValue); bool equalDimensions(const std::vector<Matcher>& dimension_a, const std::vector<Matcher>& dimension_b); diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 982a63e3e08c..325cbc7e80e5 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -138,13 +138,6 @@ void StatsLogProcessor::onPeriodicAlarmFired( } } -void updateUid(Value* value, int hostUid) { - int uid = value->int_value; - if (uid != hostUid) { - value->setInt(hostUid); - } -} - void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) != android::util::AtomsInfo::kAtomsWithAttributionChain.end()) { @@ -154,22 +147,15 @@ void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event } if (isAttributionUidField(value)) { const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value); - updateUid(&value.mValue, hostUid); + value.mValue.setInt(hostUid); } } } else { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId()); - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto, - // starting from 1 - if (uidField > 0 && (int)event->getValues().size() >= uidField && - (event->getValues())[uidField - 1].mValue.getType() == INT) { - Value& value = (*event->getMutableValues())[uidField - 1].mValue; - const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); - updateUid(&value, hostUid); - } else { - ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); - } + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); } } } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index bd15264c008f..453ddeb332af 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -384,12 +384,12 @@ message Atom { PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; VmsClientConnectionStateChanged vms_client_connection_state_changed = 230 [(module) = "car"]; - MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"]; - MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"]; - MediaProviderPermissionEvent media_provider_permission_event = + MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"]; + MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"]; + MediaProviderPermissionRequested media_provider_permission_requested = 235 [(module) = "mediaprovider"]; - MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"]; - MediaProviderIdleMaintenance media_provider_idle_maintenance = + MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"]; + MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished = 237 [(module) = "mediaprovider"]; RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"]; BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"]; @@ -418,7 +418,9 @@ message Atom { AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"]; SharesheetStarted sharesheet_started = 259 [(module) = "framework"]; RankingSelected ranking_selected = 260 [(module) = "framework"]; - TvSettingsUIInteracted tvsettings_ui_interacted = 261; + TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"]; + LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"]; + PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; } @@ -2979,14 +2981,98 @@ message ExclusionRectStateChanged { optional int32 duration_millis = 7; } +/** + * Logs when Launcher (HomeScreen) UI has changed or was interacted. + * + * Logged from: + * packages/apps/Launcher3 + */ message LauncherUIChanged { - optional android.stats.launcher.LauncherAction action = 1; + optional android.stats.launcher.LauncherAction action = 1 [deprecated = true]; optional android.stats.launcher.LauncherState src_state = 2; optional android.stats.launcher.LauncherState dst_state = 3; - optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES]; - optional bool is_swipe_up_enabled = 5; + optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES, deprecated = true]; + optional bool is_swipe_up_enabled = 5 [deprecated = true]; + + // The event id (e.g., app launch, drag and drop, long press) + optional int32 event_id = 6; + // The event's source or target id (e.g., icon, task, button) + optional int32 target_id = 7; + // If the target needs to be tracked, use this id field + optional int32 instance_id = 8; + optional int32 uid = 9 [(is_uid) = true]; + optional string package_name = 10; + optional string component_name = 11; + + // (x, y) coordinate and the index information of the target on the container + optional int32 grid_x = 12; + optional int32 grid_y = 13; + optional int32 page_id = 14; + + // e.g., folder icon's (x, y) location and index information on the workspace + optional int32 grid_x_parent = 15; + optional int32 grid_y_parent = 16; + optional int32 page_id_parent = 17; + + // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE + optional int32 hierarchy = 18; + + optional bool is_work_profile = 19; + + // Used to store the predicted rank of the target + optional int32 rank = 20; + + // e.g., folderLabelState can be captured in the following two fields + optional int32 from_state = 21; + optional int32 to_state = 22; + + // e.g., autofilled or suggested texts that are not user entered + optional string edittext = 23; +} + +/** + * Used for snapshot of the HomeScreen UI elements + * + * Logged from: + * packages/apps/Launcher3 + */ +message LauncherStaticLayout { + // The event id (e.g., snapshot, drag and drop) + optional int32 event_id = 1; + // The event's source or target id (e.g., icon, shortcut, widget) + optional int32 target_id = 2; + // If the target needs to be tracked, use this id field + optional int32 instance_id = 3; + optional int32 uid = 4 [(is_uid) = true]; + optional string package_name = 5; + optional string component_name = 6; + + // (x, y) coordinate and the index information of the target on the container + optional int32 grid_x = 7; + optional int32 grid_y = 8; + optional int32 page_id = 9; + + // e.g., folder icon's (x, y) location and index information on the workspace + optional int32 grid_x_parent = 10; + optional int32 grid_y_parent = 11; + optional int32 page_id_parent = 12; + + // e.g., WORKSPACE, HOTSEAT, FOLDER_WORKSPACE, FOLDER_HOTSEAT + optional int32 hierarchy = 13; + + optional bool is_work_profile = 14; + + // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION + optional int32 origin = 15; } +/** + * Logs when Wallpaper or ThemePicker UI has changed. + * + * Logged from: + * packages/apps/ThemePicker + * packages/apps/WallpaperPicker2 + */ message StyleUIChanged { optional android.stats.style.Action action = 1; optional int32 color_package_hash = 2; @@ -4370,7 +4456,7 @@ message VmsClientConnectionStateChanged { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java */ -message MediaProviderScanEvent { +message MediaProviderScanOccurred { enum Reason { // Scan triggered due to unknown reason UNKNOWN = 0; @@ -4404,15 +4490,13 @@ message MediaProviderScanEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java */ -message MediaProviderDeletionEvent { +message MediaContentDeleted { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; - // Device timestamp when this deletion event occurred - optional int64 timestamp_millis = 2; - // App that requested deletion - optional string package_name = 3; + // UID of app that requested deletion + optional int32 uid = 2 [(is_uid) = true]; // Number of items that were deleted - optional int32 item_count = 4; + optional int32 item_count = 3; } /** @@ -4421,7 +4505,7 @@ message MediaProviderDeletionEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java */ -message MediaProviderPermissionEvent { +message MediaProviderPermissionRequested { enum Result { UNKNOWN = 0; USER_GRANTED = 1; @@ -4433,14 +4517,12 @@ message MediaProviderPermissionEvent { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; - // Device timestamp when this permission event occurred - optional int64 timestamp_millis = 2; - // App that requested permission - optional string package_name = 3; + // UID of app that requested permission + optional int32 uid = 2 [(is_uid) = true]; // Number of items that were requested - optional int32 item_count = 4; + optional int32 item_count = 3; // Result of this request - optional Result result = 5; + optional Result result = 4; } /** @@ -4449,7 +4531,7 @@ message MediaProviderPermissionEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java */ -message MediaProviderSchemaChange { +message MediaProviderSchemaChanged { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; // Old database version code @@ -4468,7 +4550,7 @@ message MediaProviderSchemaChange { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java */ -message MediaProviderIdleMaintenance { +message MediaProviderIdleMaintenanceFinished { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; @@ -9223,6 +9305,25 @@ message TvSettingsUIInteracted { } /** + * Logs information about a package installation using package installer V2 APIs. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java + */ +message PackageInstallerV2Reported { + // Whether this installation uses Incremental File System + optional bool is_incremental = 1; + // Name of the package that is intended to be installed + optional string package_name = 2; + // The duration between when the install was requested to when the install has completed + optional int64 duration_millis = 3; + // Installation result in final integer, which are SystemApi's. + // Return_code 1 indicates success. + // For full list, see frameworks/base/core/java/android/content/pm/PackageManager.java + optional int32 return_code = 4; +} + +/** * Logs settings provider values. * * Use DeviceConfig.getProperties to get a list Setting key, query the data from content provider, diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 6d9c644bb40e..bbae3fef7934 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -43,20 +43,23 @@ using android::base::StringPrintf; using std::unique_ptr; struct ConfigReceiverDeathCookie { - ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey, - const shared_ptr<IPendingIntentRef>& pir): - mConfigManager(configManager), - mConfigKey(configKey), - mPir(pir) {} + ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey, + const shared_ptr<IPendingIntentRef>& pir) : + mConfigManager(configManager), mConfigKey(configKey), mPir(pir) { + } - sp<ConfigManager> mConfigManager; + wp<ConfigManager> mConfigManager; ConfigKey mConfigKey; shared_ptr<IPendingIntentRef> mPir; }; void ConfigManager::configReceiverDied(void* cookie) { auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie); - sp<ConfigManager>& thiz = cookie_->mConfigManager; + sp<ConfigManager> thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + ConfigKey& configKey = cookie_->mConfigKey; shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; @@ -74,20 +77,23 @@ void ConfigManager::configReceiverDied(void* cookie) { } struct ActiveConfigChangedReceiverDeathCookie { - ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid, - const shared_ptr<IPendingIntentRef>& pir): - mConfigManager(configManager), - mUid(uid), - mPir(pir) {} + ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid, + const shared_ptr<IPendingIntentRef>& pir) : + mConfigManager(configManager), mUid(uid), mPir(pir) { + } - sp<ConfigManager> mConfigManager; + wp<ConfigManager> mConfigManager; int mUid; shared_ptr<IPendingIntentRef> mPir; }; void ConfigManager::activeConfigChangedReceiverDied(void* cookie) { auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie); - sp<ConfigManager>& thiz = cookie_->mConfigManager; + sp<ConfigManager> thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + int uid = cookie_->mUid; shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 79a7e8d318e2..ebe961014336 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -44,19 +44,23 @@ namespace statsd { // Stores the puller as a wp to avoid holding a reference in case it is unregistered and // pullAtomCallbackDied is never called. struct PullAtomCallbackDeathCookie { - PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey, - const wp<StatsPuller>& puller) - : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { + PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager, + const PullerKey& pullerKey, const wp<StatsPuller>& puller) : + mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { } - sp<StatsPullerManager> mPullerManager; + wp<StatsPullerManager> mPullerManager; PullerKey mPullerKey; wp<StatsPuller> mPuller; }; void StatsPullerManager::pullAtomCallbackDied(void* cookie) { PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie); - sp<StatsPullerManager>& thiz = cookie_->mPullerManager; + sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote(); + if (!thiz) { + return; + } + const PullerKey& pullerKey = cookie_->mPullerKey; wp<StatsPuller> puller = cookie_->mPuller; diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp index aee725698c30..90247cf9d68c 100644 --- a/cmds/statsd/src/external/puller_util.cpp +++ b/cmds/statsd/src/external/puller_util.cpp @@ -49,10 +49,14 @@ using namespace std; */ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap, int tagId, const vector<int>& additiveFieldsVec) { - if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) == - android::util::AtomsInfo::kAtomsWithAttributionChain.end()) && - (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) == - android::util::AtomsInfo::kAtomsWithUidField.end())) { + bool hasAttributionChain = (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) != + android::util::AtomsInfo::kAtomsWithAttributionChain.end()); + // To check if any LogEvent has a uid field, we can just check the first + // LogEvent because all atoms with this tagId should have the uid + // annotation. + bool hasUidField = (data[0]->getUidFieldIndex() != -1); + + if (!hasAttributionChain && !hasUidField) { VLOG("No uid or attribution chain to merge, atom %d", tagId); return; } @@ -75,19 +79,13 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const } } } else { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId); - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto, - // starting from 1 - if (uidField > 0 && (int)event->getValues().size() >= uidField && - (event->getValues())[uidField - 1].mValue.getType() == INT) { - Value& value = (*event->getMutableValues())[uidField - 1].mValue; - const int hostUid = uidMap->getHostUidOrSelf(value.int_value); - value.setInt(hostUid); - } else { - ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); - return; - } + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = uidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); + } else { + ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); } } } diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 96bf04f4f6d6..8b6a86464155 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -240,14 +240,20 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, last[1] = last[2] = false; } +// Assumes that mValues is not empty +bool LogEvent::checkPreviousValueType(Type expected) { + return mValues[mValues.size() - 1].mValue.getType() == expected; +} + void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { + if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) { mValid = false; return; } bool isUid = readNextValue<uint8_t>(); if (isUid) mUidFieldIndex = mValues.size() - 1; + mValues[mValues.size() - 1].mAnnotations.setUidField(isUid); } void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 850e2b010f8c..4eeb7d64a463 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -213,14 +213,12 @@ private: void parseExclusiveStateAnnotation(uint8_t annotationType); void parseTriggerStateResetAnnotation(uint8_t annotationType); void parseStateNestedAnnotation(uint8_t annotationType); + bool checkPreviousValueType(Type expected); /** * The below three variables are only valid during the execution of * parseBuffer. There are no guarantees about the state of these variables * before/after. - * - * TODO (b/150312423): These shouldn't be member variables. We should pass - * them around as parameters. */ uint8_t* mBuf; uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 1f8bbd7f528c..2b4c6a3cbf1e 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -81,18 +81,17 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera return matched; } -bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value, - const string& str_match) { - if (isAttributionUidField(field, value) || isUidField(field, value)) { - int uid = value.int_value; +bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) { + if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) { + int uid = fieldValue.mValue.int_value; auto aidIt = UidMap::sAidToUidMapping.find(str_match); if (aidIt != UidMap::sAidToUidMapping.end()) { return ((int)aidIt->second) == uid; } std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/); return packageNames.find(str_match) != packageNames.end(); - } else if (value.getType() == STRING) { - return value.str_value == str_match; + } else if (fieldValue.mValue.getType() == STRING) { + return fieldValue.mValue.str_value == str_match; } return false; } @@ -228,8 +227,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } case FieldValueMatcher::ValueMatcherCase::kEqString: { for (int i = start; i < end; i++) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, - matcher.eq_string())) { + if (tryMatchString(uidMap, values[i], matcher.eq_string())) { return true; } } @@ -240,7 +238,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, for (int i = start; i < end; i++) { bool notEqAll = true; for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) { + if (tryMatchString(uidMap, values[i], str)) { notEqAll = false; break; } @@ -255,7 +253,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, const auto& str_list = matcher.eq_any_string(); for (int i = start; i < end; i++) { for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) { + if (tryMatchString(uidMap, values[i], str)) { return true; } } diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp index 1cee4d9babdc..bed836a1bd90 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.cpp +++ b/cmds/statsd/src/shell/ShellSubscriber.cpp @@ -152,7 +152,6 @@ void ShellSubscriber::startPull(int64_t myToken) { mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, &data); VLOG("pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id()); - // TODO(b/150969574): Don't write to a pipe while holding a lock. if (!writePulledAtomsLocked(data, pullInfo.mPullerMatcher)) { mSubscriptionInfo->mClientAlive = false; mSubscriptionShouldEnd.notify_one(); diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index 0bf24f1d3606..f5ba8fd0d60d 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -41,22 +41,10 @@ void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timest AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -66,22 +54,10 @@ void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timest AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // anonymous namespace diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp index 6f4c8e48eff2..26423d464027 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -18,6 +18,7 @@ #include <log/logprint.h> #include <stdio.h> +#include "annotations.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "matchers/matcher_util.h" #include "stats_event.h" @@ -48,15 +49,9 @@ void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t tim AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -64,15 +59,9 @@ void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t t AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeFloat(statsEvent, floatValue); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -80,32 +69,20 @@ void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } -void makeIntStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const int32_t value, const string& name) { +void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId, + const int32_t field, const uint8_t annotationId, + const bool annotationValue) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); + AStatsEvent_writeInt32(statsEvent, field); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -115,22 +92,10 @@ void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -141,13 +106,8 @@ void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t ti AStatsEvent_writeBool(statsEvent, bool1); AStatsEvent_writeBool(statsEvent, bool2); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // anonymous namespace @@ -416,21 +376,20 @@ TEST(AtomMatcherTest, TestUidFieldMatcher) { simpleMatcher->add_field_value_matcher()->set_field(1); simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0"); - // Set up the event + // Make event without is_uid annotation. LogEvent event1(/*uid=*/0, /*pid=*/0); makeIntLogEvent(&event1, TAG_ID, 0, 1111); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + // Make event with is_uid annotation. LogEvent event2(/*uid=*/0, /*pid=*/0); - makeIntStringLogEvent(&event2, TAG_ID_2, 0, 1111, "some value"); - - // Tag not in kAtomsWithUidField - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true); - // Tag found in kAtomsWithUidField and has matching uid + // Event has is_uid annotation, so mapping from uid to package name occurs. simpleMatcher->set_atom_id(TAG_ID_2); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); - // Tag found in kAtomsWithUidField but has non-matching uid + // Event has is_uid annotation, but uid maps to different package name. simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); } diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index 7febb35355a3..ba5b032b80d0 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -67,22 +67,12 @@ void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp, AStatsEvent_overwriteTimestamp(statsEvent, timestamp); vector<std::string> tags(uids.size()); // vector of empty strings - vector<const char*> cTags(uids.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = tags[i].c_str(); - } - AStatsEvent_writeAttributionChain(statsEvent, reinterpret_cast<const uint32_t*>(uids.data()), - cTags.data(), uids.size()); + writeAttribution(statsEvent, uids, tags); AStatsEvent_writeString(statsEvent, wl.c_str()); AStatsEvent_writeInt32(statsEvent, acquire); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // anonymous namespace diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp index 81e1c05c1cf4..60403f2a3e0f 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp @@ -84,14 +84,9 @@ std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str()); AStatsEvent_writeInt32(statsEvent, is_instant_app); AStatsEvent_writeInt32(statsEvent, activity_start_msec); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp index 6aff9ef80a71..4b9bac127dc8 100644 --- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -190,13 +190,13 @@ TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) { int32_t uid = 123; values.push_back(value); - StatsPullerManager pullerManager; - pullerManager.RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs, - vector<int32_t>(), cb); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs, + vector<int32_t>(), cb); vector<shared_ptr<LogEvent>> dataHolder; int64_t startTimeNs = getElapsedRealtimeNs(); // Returns false, since StatsPuller code will evaluate the timeout. - EXPECT_FALSE(pullerManager.Pull(pullTagId, {uid}, &dataHolder)); + EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, &dataHolder)); int64_t endTimeNs = getElapsedRealtimeNs(); int64_t actualPullDurationNs = endTimeNs - startTimeNs; diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp index e8200d5c7f52..504335845cab 100644 --- a/cmds/statsd/tests/external/StatsPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsPuller_test.cpp @@ -64,16 +64,10 @@ std::unique_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, pullTagId); AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - AStatsEvent_writeInt64(statsEvent, value); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp index 15425d8810dc..c2cfb371d329 100644 --- a/cmds/statsd/tests/external/puller_util_test.cpp +++ b/cmds/statsd/tests/external/puller_util_test.cpp @@ -21,8 +21,10 @@ #include <vector> #include "../metrics/metrics_test_helper.h" +#include "annotations.h" #include "stats_event.h" #include "statslog_statsdtest.h" +#include "tests/statsd_test_util.h" #ifdef __ANDROID__ @@ -52,15 +54,15 @@ int hostNonAdditiveData = 22; void extractIntoVector(vector<shared_ptr<LogEvent>> events, vector<vector<int>>& ret) { - ret.clear(); - status_t err; - for (const auto& event : events) { - vector<int> vec; - vec.push_back(event->GetInt(1, &err)); - vec.push_back(event->GetInt(2, &err)); - vec.push_back(event->GetInt(3, &err)); - ret.push_back(vec); - } + ret.clear(); + status_t err; + for (const auto& event : events) { + vector<int> vec; + vec.push_back(event->GetInt(1, &err)); + vec.push_back(event->GetInt(2, &err)); + vec.push_back(event->GetInt(3, &err)); + ret.push_back(vec); + } } std::shared_ptr<LogEvent> makeUidLogEvent(uint64_t timestampNs, int uid, int data1, int data2) { @@ -69,16 +71,12 @@ std::shared_ptr<LogEvent> makeUidLogEvent(uint64_t timestampNs, int uid, int dat AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); AStatsEvent_writeInt32(statsEvent, data1); AStatsEvent_writeInt32(statsEvent, data2); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -86,16 +84,10 @@ std::shared_ptr<LogEvent> makeNonUidAtomLogEvent(uint64_t timestampNs, int data1 AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, nonUidAtomTagId); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, data1); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp index 6dc041f9fb6e..a15f95bef358 100644 --- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp +++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp @@ -21,6 +21,7 @@ #include <thread> #include "stats_event.h" +#include "tests/statsd_test_util.h" namespace android { namespace os { @@ -37,14 +38,9 @@ std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, 10); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index d55996cb1b7a..65f8de69711d 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -46,26 +46,17 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeString(statsEvent, uid.c_str()); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // namespace diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 6143dc0dc5d1..30f815962160 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -26,6 +26,7 @@ #include "src/condition/ConditionWizard.h" #include "src/stats_log_util.h" #include "stats_event.h" +#include "tests/statsd_test_util.h" using namespace android::os::statsd; using namespace testing; @@ -48,12 +49,8 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // namespace diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index e58bbb7893d7..97647a7e0867 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -43,14 +43,9 @@ void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, strin AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeString(statsEvent, str.c_str()); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } } // anonymous namespace diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 2fe05a4430c3..42d0d5d8c530 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -64,14 +64,9 @@ shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t v AStatsEvent_writeInt32(statsEvent, value1); AStatsEvent_writeString(statsEvent, str1.c_str()); AStatsEvent_writeInt32(statsEvent, value2); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); - + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } } // anonymous namespace diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index b623a0978f18..009e49a5523f 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -611,7 +611,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { vector<shared_ptr<LogEvent>> allData; allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); @@ -656,7 +656,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -665,14 +665,14 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); // Next value should create a new bucket. LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10); + CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); @@ -812,10 +812,10 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice @@ -856,7 +856,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { valueProducer.mCondition = ConditionState::kFalse; LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has 1 slice EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); @@ -864,7 +864,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice @@ -875,7 +875,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { EXPECT_EQ(20, curInterval.value.long_value); LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 1, 30); + CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); // has one slice @@ -886,7 +886,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 1, 40); + CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); // has one slice @@ -1195,10 +1195,10 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice @@ -1238,10 +1238,10 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice @@ -1283,10 +1283,10 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -1331,10 +1331,10 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -1374,10 +1374,10 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { valueProducer.prepareFirstBucket(); LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -1398,7 +1398,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { // no change in data. LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -1408,7 +1408,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { EXPECT_EQ(true, curInterval.hasValue); LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15); + CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -2166,7 +2166,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 1, 1, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); @@ -2174,7 +2174,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) // Bucket end. allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 140)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp index ac3ad690f81e..7b952d7a392e 100644 --- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp +++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp @@ -171,13 +171,9 @@ shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) { AStatsEvent_overwriteTimestamp(statsEvent, 1111L); AStatsEvent_writeInt32(statsEvent, uid); AStatsEvent_writeInt64(statsEvent, timeMillis); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index a5b8e1c50c33..78c80bc8307c 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -62,7 +62,7 @@ int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& query // START: build event functions. // Incorrect event - missing fields -std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, +std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); @@ -72,14 +72,9 @@ std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& AStatsEvent_writeString(statsEvent, packageName.c_str()); // Missing field 3 - using_alert_window. AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -93,14 +88,9 @@ std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri AStatsEvent_writeString(statsEvent, packageName.c_str()); AStatsEvent_writeInt32(statsEvent, true); // using_alert_window AStatsEvent_writeString(statsEvent, "string"); // exclusive state: string instead of int - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } // END: build event functions. diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 7d765d3fbbf5..ed3cf5b96b42 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -507,23 +507,26 @@ void getPartialWakelockKey(int uid, HashableDimensionKey* key) { } // END: get primary key functions -shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags) { + vector<const char*> cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } - AStatsEvent_writeInt32(statsEvent, value1); - AStatsEvent_writeInt32(statsEvent, value2); + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast<const uint32_t*>(attributionUids.data()), + cTags.data(), attributionUids.size()); +} + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { AStatsEvent_build(statsEvent); size_t size; uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); - return logEvent; + AStatsEvent_release(statsEvent); } void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, @@ -534,31 +537,14 @@ void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, AStatsEvent_writeInt32(statsEvent, value1); AStatsEvent_writeInt32(statsEvent, value2); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } -shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2, int32_t value3) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, value1); - AStatsEvent_writeInt32(statsEvent, value2); - AStatsEvent_writeInt32(statsEvent, value3); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); +shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2) { shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); - + CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2); return logEvent; } @@ -571,29 +557,14 @@ void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeN AStatsEvent_writeInt32(statsEvent, value1); AStatsEvent_writeInt32(statsEvent, value2); AStatsEvent_writeInt32(statsEvent, value3); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } -shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); +shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3) { shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); - + CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3); return logEvent; } @@ -605,26 +576,13 @@ void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTi AStatsEvent_writeInt32(statsEvent, value); AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } -shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); +shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) { shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); - + CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value); return logEvent; } @@ -632,12 +590,14 @@ void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - AStatsEvent_build(statsEvent); - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) { + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs); + return logEvent; } std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( @@ -645,16 +605,10 @@ std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -662,16 +616,10 @@ std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -679,16 +627,10 @@ std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -696,16 +638,10 @@ std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestam AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -713,16 +649,10 @@ std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampN AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, level); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -733,24 +663,12 @@ std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, jobName.c_str()); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -780,25 +698,13 @@ std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs, AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); AStatsEvent_writeString(statsEvent, wakelockName.c_str()); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -828,14 +734,9 @@ std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( AStatsEvent_writeString(statsEvent, "pkg_name"); AStatsEvent_writeString(statsEvent, "class_name"); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -858,24 +759,12 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeString(statsEvent, name.c_str()); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -904,14 +793,9 @@ std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( AStatsEvent_writeInt32(statsEvent, uid); AStatsEvent_writeString(statsEvent, ""); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -928,14 +812,9 @@ std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, cons AStatsEvent_writeInt32(statsEvent, uid); AStatsEvent_writeString(statsEvent, "eventType"); AStatsEvent_writeString(statsEvent, "processName"); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -948,14 +827,9 @@ std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, in AStatsEvent_writeInt32(statsEvent, hostUid); AStatsEvent_writeInt32(statsEvent, isolatedUid); AStatsEvent_writeInt32(statsEvent, is_create); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -967,14 +841,9 @@ std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent( AStatsEvent_writeInt32(statsEvent, uid); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -988,26 +857,14 @@ std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs, AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - vector<const char*> cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast<const uint32_t*>(attributionUids.data()), - cTags.data(), attributionUids.size()); + writeAttribution(statsEvent, attributionUids, attributionTags); AStatsEvent_writeInt32(statsEvent, state); AStatsEvent_writeInt32(statsEvent, filtered); // filtered AStatsEvent_writeInt32(statsEvent, firstMatch); // first match AStatsEvent_writeInt32(statsEvent, opportunistic); // opportunistic - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -1023,14 +880,9 @@ std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, co AStatsEvent_writeString(statsEvent, packageName.c_str()); AStatsEvent_writeInt32(statsEvent, usingAlertWindow); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); - logEvent->parseBuffer(buf, size); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index f24705a0c89f..d6ea77eb2c7d 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -25,6 +25,7 @@ #include "src/hash.h" #include "src/logd/LogEvent.h" #include "src/stats_log_util.h" +#include "stats_event.h" #include "statslog_statsdtest.h" namespace android { @@ -189,6 +190,12 @@ void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey void getPartialWakelockKey(int uid, HashableDimensionKey* key); // END: get primary key functions +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags); + +// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent. +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, int32_t value2); diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index 2a7cfd306174..54a744b654cb 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -64,10 +64,12 @@ public class TestDrive { "AID_LMKD", "com.android.managedprovisioning", "AID_MEDIA", - "AID_NETWORK_STACK" + "AID_NETWORK_STACK", + "com.google.android.providers.media.module", }; private static final String[] DEFAULT_PULL_SOURCES = { "AID_SYSTEM", + "AID_RADIO" }; private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java index 3180b77f5700..957ebfbef799 100644 --- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java +++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.sysprop.InitProperties; public class PowerCommand extends Svc.Command { private static final int FORCE_SUSPEND_DELAY_DEFAULT_MILLIS = 0; @@ -103,6 +104,8 @@ public class PowerCommand extends Svc.Command { pm.reboot(false, mode, true); } catch (RemoteException e) { maybeLogRemoteException("Failed to reboot."); + } catch (Exception e) { + System.err.println("Failed to reboot: " + e.getMessage()); } return; } else if ("shutdown".equals(args[1])) { @@ -138,7 +141,9 @@ public class PowerCommand extends Svc.Command { // if it is already in shutdown flow. private void maybeLogRemoteException(String msg) { String powerProp = SystemProperties.get("sys.powerctl"); - if (powerProp.isEmpty()) { + // Also check if userspace reboot is ongoing, since in case of userspace reboot value of the + // sys.powerctl property will be reset. + if (powerProp.isEmpty() && !InitProperties.userspace_reboot_in_progress().orElse(false)) { System.err.println(msg); } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index f3759fd611df..67334f554df7 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -47,7 +47,6 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; -import android.view.SurfaceControl; import android.view.SurfaceView; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -1820,6 +1819,13 @@ public abstract class AccessibilityService extends Service { /** * Returns a list of system actions available in the system right now. + * <p> + * System actions that correspond to the global action constants will have matching action IDs. + * For example, an with id {@link #GLOBAL_ACTION_BACK} will perform the back action. + * </p> + * <p> + * These actions should be called by {@link #performGlobalAction}. + * </p> * * @return A list of available system actions. */ @@ -1981,8 +1987,6 @@ public abstract class AccessibilityService extends Service { * to declare the capability to take screenshot by setting the * {@link android.R.styleable#AccessibilityService_canTakeScreenshot} * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. - * This API only will support {@link Display#DEFAULT_DISPLAY} until {@link SurfaceControl} - * supports non-default displays. * </p> * * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for @@ -1990,18 +1994,11 @@ public abstract class AccessibilityService extends Service { * @param executor Executor on which to run the callback. * @param callback The callback invoked when taking screenshot has succeeded or failed. * See {@link TakeScreenshotCallback} for details. - * - * @throws IllegalArgumentException if displayId is not {@link Display#DEFAULT_DISPLAY}. */ public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, @NonNull TakeScreenshotCallback callback) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null"); - - if (displayId != Display.DEFAULT_DISPLAY) { - throw new IllegalArgumentException("DisplayId isn't the default display"); - } - final IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance().getConnection( mConnectionId); diff --git a/core/java/android/annotation/NonNull.java b/core/java/android/annotation/NonNull.java index a95bf3b8061e..c5aff9d6794e 100644 --- a/core/java/android/annotation/NonNull.java +++ b/core/java/android/annotation/NonNull.java @@ -30,8 +30,8 @@ import java.lang.annotation.Target; * <p> * This is a marker annotation and it has no specific attributes. * - * @paramDoc This value must never be {@code null}. - * @returnDoc This value will never be {@code null}. + * @paramDoc This value cannot be {@code null}. + * @returnDoc This value cannot be {@code null}. * @hide */ @Retention(SOURCE) diff --git a/core/java/android/app/AppOps.md b/core/java/android/app/AppOps.md index bee701addca8..ad1a30146663 100644 --- a/core/java/android/app/AppOps.md +++ b/core/java/android/app/AppOps.md @@ -116,14 +116,27 @@ for app-ops. It also delays the changes by a _settle time_. This delay is needed can fluctuate when switching apps. By delaying the change the appops service is not affected by those. -The proc state is used for two use cases: Firstly, Tracking remembers the proc state for each -tracked event. Secondly, `noteOp`/`checkOp` calls for app-op that are set to `MODE_FOREGROUND` are -translated using the `AppOpsService.UidState.evalMode` method into `MODE_ALLOWED` when the app is -counted as foreground and `MODE_IGNORED` when the app is counted as background. `checkOpRaw` -calls are not affected. - -The current proc state for an app can be read from `dumpsys appops`. The tracking information can -be read from `dumpsys appops` +In addition to proc state, the `AppOpsService` also receives process capability update from the +`ActivityManagerService`. Proc capability specifies what while-in-use(`MODE_FOREGROUND`) operations + the proc is allowed to perform in its current proc state. There are three proc capabilities + defined so far: +`PROCESS_CAPABILITY_FOREGROUND_LOCATION`, `PROCESS_CAPABILITY_FOREGROUND_CAMERA` and +`PROCESS_CAPABILITY_FOREGROUND_MICROPHONE`, they correspond to the while-in-use operation of +location, camera and microphone (microphone is `RECORD_AUDIO`). + +In `ActivityManagerService`, `PROCESS_STATE_TOP` and `PROCESS_STATE_PERSISTENT` have all +three capabilities, `PROCESS_STATE_FOREGROUND_SERVICE` has capabilities defined by + `foregroundServiceType` that is specified in foreground service's manifest file. A client process + can pass its capabilities to service using `BIND_INCLUDE_CAPABILITIES` flag. + +The proc state and capability are used for two use cases: Firstly, Tracking remembers the proc state + for each tracked event. Secondly, `noteOp`/`checkOp` calls for app-op that are set to + `MODE_FOREGROUND` are translated using the `AppOpsService.UidState.evalMode` method into + `MODE_ALLOWED` when the app has the capability and `MODE_IGNORED` when the app does not have the + capability. `checkOpRaw` calls are not affected. + +The current proc state and capability for an app can be read from `dumpsys appops`. +The tracking information can be read from `dumpsys appops` ``` Uid u0a118: diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 46b06fb64b80..3a708a6f699b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1395,6 +1395,7 @@ public class AppOpsManager { public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages"; /** @hide Access all external storage */ @SystemApi + @TestApi public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage"; diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index e476993f003f..b7ceb6ae1b4c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -299,7 +299,6 @@ interface IActivityTaskManager { in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations); void suppressResizeConfigChanges(boolean suppress); - void moveTasksToFullscreenStack(int fromStackId, boolean onTop); boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds); boolean isInMultiWindowMode(in IBinder token); boolean isInPictureInPictureMode(in IBinder token); diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl index 8ea881fba09c..3627b0f13a7f 100644 --- a/core/java/android/app/IWindowToken.aidl +++ b/core/java/android/app/IWindowToken.aidl @@ -30,4 +30,6 @@ import android.view.IWindow; */ oneway interface IWindowToken { void onConfigurationChanged(in Configuration newConfig, int newDisplayId); + + void onWindowTokenRemoved(); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index f461a1708373..babeffc120bb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7158,7 +7158,8 @@ public class Notification implements Parcelable CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; - if (!TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { + if (mConversationType == CONVERSATION_TYPE_LEGACY + && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { return conversationTitle; } return null; @@ -7588,10 +7589,8 @@ public class Notification implements Parcelable >= Build.VERSION_CODES.P; boolean isOneToOne; CharSequence nameReplacement = null; - Icon avatarReplacement = null; if (!atLeastP) { isOneToOne = TextUtils.isEmpty(conversationTitle); - avatarReplacement = mBuilder.mN.mLargeIcon; if (hasOnlyWhiteSpaceSenders()) { isOneToOne = true; nameReplacement = conversationTitle; @@ -7640,7 +7639,7 @@ public class Notification implements Parcelable contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", - avatarReplacement); + mBuilder.mN.mLargeIcon); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", nameReplacement); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java index eaee0602f4b7..7a18b8120d7e 100644 --- a/core/java/android/app/Presentation.java +++ b/core/java/android/app/Presentation.java @@ -19,6 +19,7 @@ package android.app; import static android.content.Context.DISPLAY_SERVICE; import static android.content.Context.WINDOW_SERVICE; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -191,12 +192,16 @@ public class Presentation extends Dialog { mDisplay = display; mDisplayManager = (DisplayManager)getContext().getSystemService(DISPLAY_SERVICE); + final int windowType = + (display.getFlags() & Display.FLAG_PRIVATE) != 0 ? TYPE_PRIVATE_PRESENTATION + : TYPE_PRESENTATION; + final Window w = getWindow(); final WindowManager.LayoutParams attr = w.getAttributes(); attr.token = mToken; w.setAttributes(attr); w.setGravity(Gravity.FILL); - w.setType(TYPE_PRESENTATION); + w.setType(windowType); setCanceledOnTouchOutside(false); } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index d00366bd38f4..47ccc2f0badb 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -344,7 +344,7 @@ public class ResourcesManager { ApkAssets apkAssets = null; if (mLoadedApkAssets != null) { apkAssets = mLoadedApkAssets.get(newKey); - if (apkAssets != null) { + if (apkAssets != null && apkAssets.isUpToDate()) { return apkAssets; } } @@ -353,7 +353,7 @@ public class ResourcesManager { final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); if (apkAssetsRef != null) { apkAssets = apkAssetsRef.get(); - if (apkAssets != null) { + if (apkAssets != null && apkAssets.isUpToDate()) { if (mLoadedApkAssets != null) { mLoadedApkAssets.put(newKey, apkAssets); } @@ -1121,7 +1121,9 @@ public class ResourcesManager { daj = new DisplayAdjustments(daj); daj.setCompatibilityInfo(compat); } - daj.setConfiguration(config); + if (displayId == Display.DEFAULT_DISPLAY) { + daj.setConfiguration(config); + } DisplayMetrics dm = getDisplayMetrics(displayId, daj); if (displayId != Display.DEFAULT_DISPLAY) { applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 91a857225324..e599a5ce81ef 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1658,6 +1658,9 @@ public final class SystemServiceRegistry { public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; final int[] gates = ctx.mServiceInitializationStateArray; + boolean interrupted = false; + + T ret = null; for (;;) { boolean doInitialize = false; @@ -1665,7 +1668,8 @@ public final class SystemServiceRegistry { // Return it if we already have a cached instance. T service = (T) cache[mCacheIndex]; if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) { - return service; + ret = service; + break; // exit the for (;;) } // If we get here, there's no cached instance. @@ -1708,24 +1712,33 @@ public final class SystemServiceRegistry { cache.notifyAll(); } } - return service; + ret = service; + break; // exit the for (;;) } // The other threads will wait for the first thread to call notifyAll(), // and go back to the top and retry. synchronized (cache) { + // Repeat until the state becomes STATE_READY or STATE_NOT_FOUND. + // We can't respond to interrupts here; just like we can't in the "doInitialize" + // path, so we remember the interrupt state here and re-interrupt later. while (gates[mCacheIndex] < ContextImpl.STATE_READY) { try { + // Clear the interrupt state. + interrupted |= Thread.interrupted(); cache.wait(); } catch (InterruptedException e) { // This shouldn't normally happen, but if someone interrupts the // thread, it will. - Slog.wtf(TAG, "getService() interrupted"); - Thread.currentThread().interrupt(); - return null; + Slog.w(TAG, "getService() interrupted"); + interrupted = true; } } } } + if (interrupted) { + Thread.currentThread().interrupt(); + } + return ret; } public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException; diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java index 878993ebcd19..3a06c9d79fee 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -28,6 +28,8 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.ref.Reference; /** @@ -75,8 +77,6 @@ public class WindowContext extends ContextWrapper { // config back to the client. result = mWms.addWindowTokenWithOptions( mToken, type, getDisplayId(), options, getPackageName()); - - // TODO(window-context): remove token with a DeathObserver } catch (RemoteException e) { mOwnsToken = false; throw e.rethrowFromSystemServer(); @@ -100,6 +100,13 @@ public class WindowContext extends ContextWrapper { @Override protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** Used for test to invoke because we can't invoke finalize directly. */ + @VisibleForTesting + public void release() { if (mOwnsToken) { try { mWms.removeWindowToken(mToken, getDisplayId()); @@ -108,6 +115,12 @@ public class WindowContext extends ContextWrapper { throw e.rethrowFromSystemServer(); } } - super.finalize(); + destroy(); + } + + void destroy() { + final ContextImpl impl = (ContextImpl) getBaseContext(); + impl.scheduleFinalCleanup(getClass().getName(), "WindowContext"); + Reference.reachabilityFence(this); } } diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java index ed0179bb9839..301960ec53f9 100644 --- a/core/java/android/app/WindowTokenClient.java +++ b/core/java/android/app/WindowTokenClient.java @@ -20,6 +20,9 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.view.WindowManagerGlobal; + +import java.lang.ref.WeakReference; /** * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from @@ -31,9 +34,9 @@ import android.os.IBinder; public class WindowTokenClient extends IWindowToken.Stub { /** * Attached {@link Context} for this window token to update configuration and resources. - * Initialized by {@link #attachContext(Context)}. + * Initialized by {@link #attachContext(WindowContext)}. */ - private Context mContext = null; + private WeakReference<WindowContext> mContextRef = null; private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); @@ -47,30 +50,46 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param context context to be attached * @throws IllegalStateException if attached context has already existed. */ - void attachContext(@NonNull Context context) { - if (mContext != null) { + void attachContext(@NonNull WindowContext context) { + if (mContextRef != null) { throw new IllegalStateException("Context is already attached."); } - mContext = context; - ContextImpl impl = ContextImpl.getImpl(mContext); + mContextRef = new WeakReference<>(context); + final ContextImpl impl = ContextImpl.getImpl(context); impl.setResources(impl.createWindowContextResources()); } @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { - final int currentDisplayId = mContext.getDisplayId(); + final Context context = mContextRef.get(); + if (context == null) { + return; + } + final int currentDisplayId = context.getDisplayId(); final boolean displayChanged = newDisplayId != currentDisplayId; - final Configuration config = new Configuration(mContext.getResources() + final Configuration config = new Configuration(context.getResources() .getConfiguration()); final boolean configChanged = config.isOtherSeqNewer(newConfig) && config.updateFrom(newConfig) != 0; if (displayChanged || configChanged) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens - mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId, + mResourcesManager.updateResourcesForActivity(this, config, newDisplayId, displayChanged); } if (displayChanged) { - mContext.updateDisplay(newDisplayId); + context.updateDisplay(newDisplayId); + } + } + + @Override + public void onWindowTokenRemoved() { + final WindowContext context = mContextRef.get(); + if (context != null) { + context.destroy(); + mContextRef.clear(); } + // If a secondary display is detached, release all views attached to this token. + WindowManagerGlobal.getInstance().closeAll(this, mContextRef.getClass().getName(), + "WindowContext"); } } diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java index db833ec478bd..473725f40cf1 100644 --- a/core/java/android/app/admin/DevicePolicyKeyguardService.java +++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java @@ -16,12 +16,15 @@ package android.app.admin; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.SurfaceControlViewHost; @@ -41,27 +44,34 @@ import android.view.SurfaceControlViewHost; @SystemApi public class DevicePolicyKeyguardService extends Service { private static final String TAG = "DevicePolicyKeyguardService"; + private final Handler mHandler = new Handler(Looper.getMainLooper()); private IKeyguardCallback mCallback; private final IKeyguardClient mClient = new IKeyguardClient.Stub() { + @MainThread @Override public void onCreateKeyguardSurface(@Nullable IBinder hostInputToken, - IKeyguardCallback callback) { + @NonNull IKeyguardCallback callback) { mCallback = callback; - SurfaceControlViewHost.SurfacePackage surfacePackage = - DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken); + mHandler.post(() -> { + SurfaceControlViewHost.SurfacePackage surfacePackage = + DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken); - if (mCallback != null) { try { mCallback.onRemoteContentReady(surfacePackage); } catch (RemoteException e) { Log.e(TAG, "Failed to return created SurfacePackage", e); } - } + }); } }; @Override + public void onDestroy() { + mHandler.removeCallbacksAndMessages(null); + } + + @Override @Nullable public final IBinder onBind(@Nullable Intent intent) { return mClient.asBinder(); @@ -97,6 +107,10 @@ public class DevicePolicyKeyguardService extends Service { */ @Nullable public void dismiss() { + if (mCallback == null) { + Log.w(TAG, "KeyguardCallback was unexpectedly null"); + return; + } try { mCallback.onDismiss(); } catch (RemoteException e) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index faf9ec61ffde..fb9adb730314 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8606,7 +8606,7 @@ public class DevicePolicyManager { * <p> * This method may be called on the {@code DevicePolicyManager} instance returned from * {@link #getParentProfileInstance(ComponentName)}. Note that only a profile owner on - * an organization-deviced can affect account types on the parent profile instance. + * an organization-owned device can affect account types on the parent profile instance. * * @return a list of account types for which account management has been disabled. * diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 80fa87152d78..41f04f73aa87 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -176,19 +176,31 @@ public abstract class DevicePolicyManagerInternal { * for cross-profile communication, via {@link * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li> * <li>The default package names that are allowed to request user consent for cross-profile - * communication without being explicitly enabled by the admin , via {@link - * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li> + * communication without being explicitly enabled by the admin, via + * {@link com.android.internal.R.array#cross_profile_apps} and + * {@link com.android.internal.R.array#vendor_cross_profile_apps}.</li> * </ul> * * @return the combined set of whitelisted package names set via * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and - * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)} + * {@link com.android.internal.R.array#cross_profile_apps} and + * {@link com.android.internal.R.array#vendor_cross_profile_apps} * * @hide */ public abstract List<String> getAllCrossProfilePackages(); /** + * Returns the default package names set by the OEM that are allowed to request user consent for + * cross-profile communication without being explicitly enabled by the admin, via + * {@link com.android.internal.R.array#cross_profile_apps} and + * {@link com.android.internal.R.array#vendor_cross_profile_apps}. + * + * @hide + */ + public abstract List<String> getDefaultCrossProfilePackages(); + + /** * Sends the {@code intent} to the packages with cross profile capabilities. * * <p>This means the application must have the {@code crossProfile} property and the diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index e446f4fa5eb4..0a4627da223a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -687,6 +687,19 @@ public abstract class ContentResolver implements ContentInterface { public static final int NOTIFY_DELETE = 1 << 4; /** + * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set + * by a {@link ContentProvider} to indicate that this notification should + * not be subject to any delays when dispatching to apps running in the + * background. + * <p> + * Using this flag may negatively impact system health and performance, and + * should be used sparingly. + * + * @hide + */ + public static final int NOTIFY_NO_DELAY = 1 << 15; + + /** * No exception, throttled by app standby normally. * @hide */ diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 7578ede2648d..144a07eb4ea3 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -435,6 +435,9 @@ public class CrossProfileApps { * <p>This differs from {@link #canConfigureInteractAcrossProfiles(String)} since it will * not return {@code false} if the app is not whitelisted or not installed in the other profile. * + * <p>Note that platform-signed apps that are automatically granted the permission and are not + * whitelisted by the OEM will not be included in this list. + * * @hide */ public boolean canUserAttemptToConfigureInteractAcrossProfiles(String packageName) { diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl index f00feaeb2f5a..92df16ced8a3 100644 --- a/core/java/android/content/pm/FileSystemControlParcel.aidl +++ b/core/java/android/content/pm/FileSystemControlParcel.aidl @@ -17,6 +17,7 @@ package android.content.pm; import android.content.pm.IPackageInstallerSessionFileSystemConnector; +import android.os.incremental.IIncrementalServiceConnector; import android.os.incremental.IncrementalFileSystemControlParcel; /** @@ -26,6 +27,8 @@ import android.os.incremental.IncrementalFileSystemControlParcel; parcelable FileSystemControlParcel { // Incremental FS control descriptors. @nullable IncrementalFileSystemControlParcel incremental; + // Incremental FS service. + @nullable IIncrementalServiceConnector service; // Callback-based installation connector. @nullable IPackageInstallerSessionFileSystemConnector callback; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f48d78ac9cc3..9a2e07e9cfbd 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1542,6 +1542,14 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_PROCESS_NOT_DEFINED = -122; + /** + * Installation parse return code: system is in a minimal boot state, and the parser only + * allows the package with {@code coreApp} manifest attribute to be a valid application. + * + * @hide + */ + public static final int INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED = -123; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 12328cf32fb3..6bd8b1d9d7c0 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -21,7 +21,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.os.Build.VERSION_CODES.DONUT; import static android.os.Build.VERSION_CODES.O; @@ -229,7 +229,8 @@ public class ParsingPackageUtils { final PackageParser.PackageLite lite = ApkLiteParseUtils.parseClusterPackageLite(packageDir, 0); if (mOnlyCoreApps && !lite.coreApp) { - return input.error("Not a coreApp: " + packageDir); + return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, + "Not a coreApp: " + packageDir); } // Build the split dependency tree. @@ -251,10 +252,8 @@ public class ParsingPackageUtils { final File baseApk = new File(lite.baseCodePath); ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, lite.codePath, assets, flags); - // TODO(b/135203078): Pass original error up? if (result.isError()) { - return input.error(INSTALL_PARSE_FAILED_NOT_APK, - "Failed to parse base APK: " + baseApk); + return input.error(result); } ParsingPackage pkg = result.getResult(); @@ -291,7 +290,8 @@ public class ParsingPackageUtils { final PackageParser.PackageLite lite = ApkLiteParseUtils.parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps && !lite.coreApp) { - return input.error("Not a coreApp: " + apkFile); + return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, + "Not a coreApp: " + apkFile); } final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index d6a9f6990abe..c399bc72e438 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -40,6 +40,7 @@ import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.annotation.XmlRes; +import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -58,9 +59,11 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.Pools.SynchronizedPool; import android.util.TypedValue; +import android.view.Display; import android.view.DisplayAdjustments; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -101,6 +104,12 @@ import java.util.List; * (such as for different languages and screen sizes). This is an important aspect of developing * Android applications that are compatible on different types of devices.</p> * + * <p>After {@link Build.VERSION_CODES#R}, {@link Resources} must be obtained by + * {@link android.app.Activity} or {@link android.content.Context} created with + * {@link android.content.Context#createWindowContext(int, Bundle)}. + * {@link Application#getResources()} may report wrong values in multi-window or on secondary + * displays. + * * <p>For more information about using resources, see the documentation about <a * href="{@docRoot}guide/topics/resources/index.html">Application Resources</a>.</p> */ @@ -2024,10 +2033,20 @@ public class Resources { } /** - * Return the current display metrics that are in effect for this resource - * object. The returned object should be treated as read-only. - * - * @return The resource's current display metrics. + * Return the current display metrics that are in effect for this resource + * object. The returned object should be treated as read-only. + * + * <p>Note that the reported value may be different than the window this application is + * interested in.</p> + * + * <p>Best practices are to obtain metrics from {@link WindowManager#getCurrentWindowMetrics()} + * for window bounds, {@link Display#getRealMetrics(DisplayMetrics)} for display bounds and + * obtain density from {@link Configuration#densityDpi}. The value obtained from this API may be + * wrong if the {@link Resources} is from the context which is different than the window is + * attached such as {@link Application#getResources()}. + * <p/> + * + * @return The resource's current display metrics. */ public DisplayMetrics getDisplayMetrics() { return mResourcesImpl.getDisplayMetrics(); diff --git a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java index 6f01796c93c2..5d4b48dac46b 100644 --- a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java +++ b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java @@ -22,4 +22,9 @@ public class SQLiteCantOpenDatabaseException extends SQLiteException { public SQLiteCantOpenDatabaseException(String error) { super(error); } + + /** @hide */ + public SQLiteCantOpenDatabaseException(String error, Throwable cause) { + super(error, cause); + } } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index bcb3934a5b08..f7c96a3a02c1 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -36,6 +36,9 @@ import dalvik.system.CloseGuard; import java.io.File; import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -215,12 +218,31 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private void open() { + final String file = mConfiguration.path; final int cookie = mRecentOperations.beginOperation("open", null, null); try { - mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags, + mConnectionPtr = nativeOpen(file, mConfiguration.openFlags, mConfiguration.label, NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME, mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount); + } catch (SQLiteCantOpenDatabaseException e) { + String message = String.format("Cannot open database '%s'", file); + + final Path path = FileSystems.getDefault().getPath(file); + final Path dir = path.getParent(); + + if (!Files.isDirectory(dir)) { + message += ": Directory " + dir + " doesn't exist"; + } else if (!Files.exists(path)) { + message += ": File " + path + " doesn't exist"; + } else if (!Files.isReadable(path)) { + message += ": File " + path + " is not readable"; + } else if (Files.isDirectory(path)) { + message += ": Path " + path + " is a directory"; + } else { + message += ": Unknown reason"; + } + throw new SQLiteCantOpenDatabaseException(message, e); } finally { mRecentOperations.endOperation(cookie); } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 91dae66d08ae..aa75f6042db8 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -17,6 +17,7 @@ package android.hardware.camera2; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; @@ -100,6 +101,8 @@ public abstract class CameraMetadata<TKey> { * * @hide */ + @UnsupportedAppUsage(publicAlternatives = "This method is exposed for native " + + "{@code ACameraMetadata_fromCameraMetadata} in {@code libcamera2ndk}.") public long getNativeMetadataPtr() { if (mNativeInstance == null) { return 0; diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index ea2b9e79d99c..462110f5a795 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -17,6 +17,7 @@ package android.hardware.display; import android.annotation.Nullable; +import android.graphics.Point; import android.hardware.SensorManager; import android.os.Handler; import android.os.PowerManager; @@ -72,6 +73,15 @@ public abstract class DisplayManagerInternal { public abstract SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId); /** + * Take a screenshot without secure layer of the specified display and return a buffer. + * + * @param displayId The display id to take the screenshot of. + * @return The buffer or null if we have failed. + */ + public abstract SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer( + int displayId); + + /** * Returns information about the specified logical display. * * @param displayId The logical display id. @@ -81,6 +91,16 @@ public abstract class DisplayManagerInternal { public abstract DisplayInfo getDisplayInfo(int displayId); /** + * Returns the position of the display's projection. + * + * @param displayId The logical display id. + * @return The x, y coordinates of the display, or null if the display does not exist. The + * return object must be treated as immutable. + */ + @Nullable + public abstract Point getDisplayPosition(int displayId); + + /** * Registers a display transaction listener to provide the client a chance to * update its surfaces within the same transaction as any display layout updates. * diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index fe90a8457949..e9bcefef50c7 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -556,12 +556,12 @@ public abstract class NetworkAgent { @NonNull public Network register() { if (VDBG) log("Registering NetworkAgent"); - final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context - .getSystemService(Context.CONNECTIVITY_SERVICE); synchronized (mRegisterLock) { if (mNetwork != null) { throw new IllegalStateException("Agent already registered"); } + final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context + .getSystemService(Context.CONNECTIVITY_SERVICE); mNetwork = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(mInitialConfiguration.info), mInitialConfiguration.properties, mInitialConfiguration.capabilities, @@ -624,7 +624,9 @@ public abstract class NetworkAgent { throw new UnsupportedOperationException( "Legacy agents can't call markConnected."); } - mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); + // |reason| cannot be used by the non-legacy agents + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */, + mNetworkInfo.getExtraInfo()); queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); } @@ -638,7 +640,9 @@ public abstract class NetworkAgent { if (mIsLegacy) { throw new UnsupportedOperationException("Legacy agents can't call unregister."); } - mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); + // When unregistering an agent nobody should use the extrainfo (or reason) any more. + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */, + null /* extraInfo */); queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 9c1fb41ecc96..b7fb280efdc4 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -1195,18 +1195,24 @@ public final class NetworkStats implements Parcelable { /** * Remove all rows that match one of specified UIDs. + * This mutates the original structure in place. * @hide */ public void removeUids(int[] uids) { - int nextOutputEntry = 0; - for (int i = 0; i < size; i++) { - if (!ArrayUtils.contains(uids, uid[i])) { - maybeCopyEntry(nextOutputEntry, i); - nextOutputEntry++; - } - } + filter(e -> !ArrayUtils.contains(uids, e.uid)); + } - size = nextOutputEntry; + /** + * Remove all rows that match one of specified UIDs. + * @return the result object. + * @hide + */ + @NonNull + public NetworkStats removeEmptyEntries() { + final NetworkStats ret = this.clone(); + ret.filter(e -> e.rxBytes != 0 || e.rxPackets != 0 || e.txBytes != 0 || e.txPackets != 0 + || e.operations != 0); + return ret; } /** diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 2041cfb22ea8..c87b8279c4d6 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -75,7 +75,7 @@ public class VpnManager { } /** - * Create an instance of the VpnManger with the given context. + * Create an instance of the VpnManager with the given context. * * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the * {@link Context.getSystemService()} method call. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index ae65f1d0bd05..5f8c4f5cdf27 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -1268,6 +1268,7 @@ public class Environment { public static boolean isExternalStorageLegacy(@NonNull File path) { final Context context = AppGlobals.getInitialApplication(); final int uid = context.getApplicationInfo().uid; + // Isolated processes and Instant apps are never allowed to be in scoped storage if (Process.isIsolated(uid)) { return false; } @@ -1277,8 +1278,6 @@ public class Environment { return false; } - // TODO(b/150672994): Compat flags do not override instant app and isolated process's - // behavior. boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE); boolean forceEnableScopedStorage = Compatibility.isChangeEnabled( FORCE_ENABLE_SCOPED_STORAGE); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index b8e1aa88c3a3..be2de0edda2d 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1483,12 +1483,25 @@ public final class PowerManager { return mInteractiveCache.query(null); } + + /** + * Returns {@code true} if this device supports rebooting userspace. + * + * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager} + * and {@code PowerManagerService}. + * + * @hide + */ + public static boolean isRebootingUserspaceSupportedImpl() { + return InitProperties.is_userspace_reboot_supported().orElse(false); + } + /** * Returns {@code true} if this device supports rebooting userspace. */ // TODO(b/138605180): add link to documentation once it's ready. public boolean isRebootingUserspaceSupported() { - return InitProperties.is_userspace_reboot_supported().orElse(false); + return isRebootingUserspaceSupportedImpl(); } /** diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 51f01cac8138..653a5594f495 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -102,9 +102,9 @@ public abstract class PowerManagerInternal { * * This method must only be called by the window manager. * - * @param brightness The overridden brightness, or -1 to disable the override. + * @param brightness The overridden brightness, or Float.NaN to disable the override. */ - public abstract void setScreenBrightnessOverrideFromWindowManager(int brightness); + public abstract void setScreenBrightnessOverrideFromWindowManager(float brightness); /** * Used by the window manager to override the user activity timeout based on the diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index b7b3c4fc8add..5d2c9d18c00c 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -607,6 +607,9 @@ public class Process { * started. * @param pkgDataInfoMap Map from related package names to private data directory * volume UUID and inode number. + * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory + * volume UUID and inode number. + * @param bindMountAppsData whether zygote needs to mount CE and DE data. * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data. * @param zygoteArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. @@ -631,13 +634,17 @@ public class Process { @Nullable long[] disabledCompatChanges, @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, + @Nullable Map<String, Pair<String, Long>> + whitelistedDataInfoMap, + boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] zygoteArgs) { return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges, - pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs); + pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData, + bindMountAppStorageDirs, zygoteArgs); } /** @hide */ @@ -661,7 +668,8 @@ public class Process { runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false, - disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs); + disabledCompatChanges, /* pkgDataInfoMap */ null, + /* whitelistedDataInfoMap */ null, false, false, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 5f3f14facd75..a4c99c006d80 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -333,6 +333,9 @@ public class ZygoteProcess { * started. * @param pkgDataInfoMap Map from related package names to private data directory * volume UUID and inode number. + * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory + * volume UUID and inode number. + * @param bindMountAppsData whether zygote needs to mount CE and DE data. * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data. * * @param zygoteArgs Additional arguments to supply to the Zygote process. @@ -355,6 +358,9 @@ public class ZygoteProcess { @Nullable long[] disabledCompatChanges, @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, + @Nullable Map<String, Pair<String, Long>> + whitelistedDataInfoMap, + boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] zygoteArgs) { // TODO (chriswailes): Is there a better place to check this value? @@ -367,7 +373,8 @@ public class ZygoteProcess { runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false, packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges, - pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs); + pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData, + bindMountAppStorageDirs, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -608,6 +615,9 @@ public class ZygoteProcess { * @param disabledCompatChanges a list of disabled compat changes for the process being started. * @param pkgDataInfoMap Map from related package names to private data directory volume UUID * and inode number. + * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory + * volume UUID and inode number. + * @param bindMountAppsData whether zygote needs to mount CE and DE data. * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data. * @param extraArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. @@ -631,6 +641,9 @@ public class ZygoteProcess { @Nullable long[] disabledCompatChanges, @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, + @Nullable Map<String, Pair<String, Long>> + whitelistedDataInfoMap, + boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] extraArgs) throws ZygoteStartFailedEx { @@ -728,11 +741,33 @@ public class ZygoteProcess { } argsForZygote.add(sb.toString()); } + if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(Zygote.WHITELISTED_DATA_INFO_MAP); + sb.append("="); + boolean started = false; + for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) { + if (started) { + sb.append(','); + } + started = true; + sb.append(entry.getKey()); + sb.append(','); + sb.append(entry.getValue().first); + sb.append(','); + sb.append(entry.getValue().second); + } + argsForZygote.add(sb.toString()); + } if (bindMountAppStorageDirs) { argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS); } + if (bindMountAppsData) { + argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS); + } + if (disabledCompatChanges != null && disabledCompatChanges.length > 0) { StringBuilder sb = new StringBuilder(); sb.append("--disabled-compat-changes="); @@ -1291,6 +1326,7 @@ public class ZygoteProcess { true /* startChildZygote */, null /* packageName */, ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */, null /* disabledCompatChanges */, null /* pkgDataInfoMap */, + null /* whitelistedDataInfoMap */, false /* bindMountAppsData*/, /* bindMountAppStorageDirs */ false, extraArgs); } catch (ZygoteStartFailedEx ex) { diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index d8308c7c3362..2dbaea860e2a 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -38,13 +38,6 @@ interface IIncrementalService { int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode); /** - * Changes storage params. Returns 0 on success, and -errno on failure. - * Use enableReadLogs to switch pages read logs reporting on and off. - * Returns 0 on success, and - errno on failure: permission check or remount. - */ - int setStorageParams(int storageId, boolean enableReadLogs); - - /** * Bind-mounts a path under a storage to a full path. Can be permanent or temporary. */ const int BIND_TEMPORARY = 0; diff --git a/core/java/android/os/incremental/IIncrementalServiceConnector.aidl b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl new file mode 100644 index 000000000000..5800ecf63a1e --- /dev/null +++ b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl @@ -0,0 +1,27 @@ +/* + * 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.os.incremental; + +/** @hide */ +interface IIncrementalServiceConnector { + /** + * Changes storage params. Returns 0 on success, and -errno on failure. + * Use enableReadLogs to switch pages read logs reporting on and off. + * Returns 0 on success, and - errno on failure: permission check or remount. + */ + int setStorageParams(boolean enableReadLogs); +} diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 5f01408944e8..35518db32829 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -19,13 +19,11 @@ package android.os.incremental; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; import android.os.RemoteException; -import android.system.ErrnoException; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -321,23 +319,6 @@ public final class IncrementalManager { return nativeUnsafeGetFileSignature(path); } - /** - * Sets storage parameters. - * - * @param enableReadLogs - enables or disables read logs. Caller has to have a permission. - */ - @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS) - public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException { - try { - int res = mService.setStorageParams(storageId, enableReadLogs); - if (res < 0) { - throw new ErrnoException("setStorageParams", -res); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e2d0c49d9d13..541883346e23 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1870,6 +1870,24 @@ public final class Settings { public static final String EXTRA_APP_UID = "app_uid"; /** + * Activity Action: Show power menu settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_POWER_MENU_SETTINGS = + "android.settings.ACTION_POWER_MENU_SETTINGS"; + + /** + * Activity Action: Show controls settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_DEVICE_CONTROLS_SETTINGS = + "android.settings.ACTION_DEVICE_CONTROLS_SETTINGS"; + + /** * Activity Action: Show a dialog with disabled by policy message. * <p> If an user action is disabled by policy, this dialog can be triggered to let * the user know about this. @@ -13757,6 +13775,16 @@ public final class Settings { "show_notification_channel_warnings"; /** + * When enabled, requires all notifications in the conversation section to be backed + * by a long-lived sharing shortcut + * + * The value 1 - require a shortcut, 0 - do not require a shortcut + * @hide + */ + public static final String REQUIRE_SHORTCUTS_FOR_CONVERSATIONS = + "require_shortcuts_for_conversations"; + + /** * Whether cell is enabled/disabled * @hide */ diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl index dd434b440af4..bf0bb9e2a41f 100644 --- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl +++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl @@ -17,6 +17,7 @@ package android.service.autofill; import android.os.IBinder; +import android.os.RemoteCallback; import android.service.autofill.IInlineSuggestionUiCallback; import android.service.autofill.InlinePresentation; @@ -29,4 +30,5 @@ oneway interface IInlineSuggestionRenderService { void renderSuggestion(in IInlineSuggestionUiCallback callback, in InlinePresentation presentation, int width, int height, in IBinder hostInputToken, int displayId); + void getInlineSuggestionsRendererInfo(in RemoteCallback callback); } diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java index cba6608db1b8..e3ed21ff556d 100644 --- a/core/java/android/service/autofill/InlineSuggestionRenderService.java +++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Log; import android.view.Display; @@ -128,6 +129,11 @@ public abstract class InlineSuggestionRenderService extends Service { } } + private void handleGetInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { + final Bundle rendererInfo = onGetInlineSuggestionsRendererInfo(); + callback.sendResult(rendererInfo); + } + private void sendResult(@NonNull IInlineSuggestionUiCallback callback, @Nullable SurfaceControlViewHost.SurfacePackage surface) { try { @@ -151,6 +157,13 @@ public abstract class InlineSuggestionRenderService extends Service { InlineSuggestionRenderService.this, callback, presentation, width, height, hostInputToken, displayId)); } + + @Override + public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { + mHandler.sendMessage(obtainMessage( + InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo, + InlineSuggestionRenderService.this, callback)); + } }.asBinder(); } diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index ef55f0615ec7..46cb65babc1c 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -747,7 +747,7 @@ public abstract class ContentCaptureService extends Service { void initializeForDelegate(DataShareReadAdapterDelegate delegate, DataShareReadAdapter adapter, Executor executor) { mDataShareReadAdapterHardReferences.put(delegate, adapter); - mExecutorHardReferences.remove(delegate, executor); + mExecutorHardReferences.put(delegate, executor); } Executor getExecutor(DataShareReadAdapterDelegate delegate) { diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index 05877a59368a..60373ace2d8a 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.content.pm.DataLoaderParams; import android.content.pm.DataLoaderParamsParcel; @@ -32,11 +31,11 @@ import android.content.pm.InstallationFile; import android.content.pm.InstallationFileParcel; import android.os.IBinder; import android.os.ParcelFileDescriptor; -import android.os.incremental.IncrementalManager; -import android.system.ErrnoException; import android.util.ExceptionUtils; import android.util.Slog; +import libcore.io.IoUtils; + import java.io.IOException; import java.util.Collection; @@ -118,22 +117,10 @@ public abstract class DataLoaderService extends Service { destroy(id); throw new RuntimeException(ex); } finally { - // Closing FDs. if (control.incremental != null) { - if (control.incremental.cmd != null) { - try { - control.incremental.cmd.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e); - } - } - if (control.incremental.log != null) { - try { - control.incremental.log.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e); - } - } + IoUtils.closeQuietly(control.incremental.cmd); + IoUtils.closeQuietly(control.incremental.pendingReads); + IoUtils.closeQuietly(control.incremental.log); } } } @@ -211,25 +198,6 @@ public abstract class DataLoaderService extends Service { private final long mNativeInstance; } - /* Used by native FileSystemConnector. */ - private boolean setStorageParams(int storageId, boolean enableReadLogs) { - IncrementalManager incrementalManager = (IncrementalManager) getSystemService( - Context.INCREMENTAL_SERVICE); - if (incrementalManager == null) { - Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId); - return false; - } - try { - // This has to be done directly in incrementalManager as the storage - // might be missing still. - incrementalManager.setStorageParams(storageId, enableReadLogs); - } catch (ErrnoException e) { - Slog.e(TAG, "Failed to set params for storage: " + storageId, e); - return false; - } - return true; - } - /* Native methods */ private native boolean nativeCreateDataLoader(int storageId, @NonNull FileSystemControlParcel control, diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 1beeb6554ee1..21b83c660446 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -17,8 +17,6 @@ package android.service.notification; import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; -import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ; -import static android.util.FeatureFlagUtils.isEnabled; import android.annotation.NonNull; import android.app.Notification; @@ -33,6 +31,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import com.android.internal.logging.InstanceId; @@ -477,9 +476,10 @@ public class StatusBarNotification implements Parcelable { */ public String getShortcutId(Context context) { String conversationId = getNotification().getShortcutId(); - if (isEnabled(context, NOTIF_CONVO_BYPASS_SHORTCUT_REQ) - && getNotification().getNotificationStyle() == Notification.MessagingStyle.class - && TextUtils.isEmpty(conversationId)) { + if (TextUtils.isEmpty(conversationId) + && (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 0) + && getNotification().getNotificationStyle() == Notification.MessagingStyle.class) { conversationId = getId() + getTag() + PLACEHOLDER_CONVERSATION_ID; } return conversationId; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f531e12b0748..f944dd78dc3d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -55,6 +55,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -190,6 +191,7 @@ public abstract class WallpaperService extends Service { new DisplayCutout.ParcelableWrapper(); DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); + final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); private final Point mSurfaceSize = new Point(); @@ -878,7 +880,7 @@ public abstract class WallpaperService extends Service { if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets, mDisplayCutout, inputChannel, - mInsetsState) < 0) { + mInsetsState, mTempControls) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -903,7 +905,7 @@ public abstract class WallpaperService extends Service { View.VISIBLE, 0, -1, mWinFrame, mContentInsets, mVisibleInsets, mStableInsets, mBackdropFrame, mDisplayCutout, mMergedConfiguration, mSurfaceControl, - mInsetsState, mSurfaceSize, mTmpSurfaceControl); + mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); mSurfaceControl.release(); diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java index 995014374721..b1647fe0fcf9 100644 --- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java +++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java @@ -21,7 +21,9 @@ import static android.os.Parcelable.Creator; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; @@ -64,6 +66,7 @@ import java.util.concurrent.TimeUnit; * </pre> * @hide */ +@TestApi @SystemApi public abstract class ExplicitHealthCheckService extends Service { @@ -159,6 +162,15 @@ public abstract class ExplicitHealthCheckService extends Service { } /** + * Sets {@link RemoteCallback}, for testing purpose. + * + * @hide + */ + @TestApi + public void setCallback(@Nullable RemoteCallback callback) { + mCallback = callback; + } + /** * Implementors should call this to notify the system when explicit health check passes * for {@code packageName}; */ @@ -183,6 +195,7 @@ public abstract class ExplicitHealthCheckService extends Service { * * @hide */ + @TestApi @SystemApi public static final class PackageConfig implements Parcelable { private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1); @@ -263,7 +276,7 @@ public abstract class ExplicitHealthCheckService extends Service { } @Override - public void writeToParcel(Parcel parcel, int flags) { + public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) { parcel.writeString(mPackageName); parcel.writeLong(mHealthCheckTimeoutMillis); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index faa5cf591408..cd20b357e2f1 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,8 +42,6 @@ public class FeatureFlagUtils { public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; - public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = - "settings_notif_convo_bypass_shortcut_req"; /** @hide */ public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED = "settings_do_not_restore_preserved"; @@ -66,7 +64,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "true"); DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false"); DEFAULT_FLAGS.put("settings_conditionals", "false"); - DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); // This flags guards a feature introduced in R and will be removed in the next release // (b/148367230). DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true"); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0ccb1e055c46..4469fdbb12ec 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -253,12 +253,14 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_UNKNOWN = 0; /** * Display type: Physical display connected through an internal port. * @hide */ + @TestApi public static final int TYPE_INTERNAL = 1; /** @@ -266,6 +268,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_EXTERNAL = 2; /** @@ -273,12 +276,14 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_WIFI = 3; /** * Display type: Overlay display. * @hide */ + @TestApi public static final int TYPE_OVERLAY = 4; /** @@ -286,6 +291,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_VIRTUAL = 5; /** @@ -569,6 +575,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public int getType() { return mType; } diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index a60a5cca08bd..983ab2eedf5c 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -114,4 +114,16 @@ interface IRecentsAnimationController { * animation is cancelled through fail safe mechanism. */ void setWillFinishToHome(boolean willFinishToHome); + + /** + * Stops controlling a task that is currently controlled by this recents animation. + * + * This method should be called when a task that has been received via {@link #onAnimationStart} + * or {@link #onTaskAppeared} is no longer needed. After calling this method, the task will + * either disappear from the screen, or jump to its final position in case it was the top task. + * + * @param taskId Id of the Task target to remove + * @return {@code true} when target removed successfully, {@code false} otherwise. + */ + boolean removeTask(int taskId); } diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 6eb90fc54286..925786f82e00 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -56,4 +56,10 @@ oneway interface IRecentsAnimationRunner { void onAnimationStart(in IRecentsAnimationController controller, in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2; + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(in RemoteAnimationTarget app) = 3; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 84ac90bae258..b0bacb955f80 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -124,13 +124,20 @@ interface IWindowManager * @param type Window type to be used with this token. * @param options A bundle used to pass window-related options. * @param displayId The ID of the display where this token should be added. - * @param packageName The name of package to request to add window token. + * @param packageName The name of package to request to add window token. Could be {@code null} + * if callers holds the MANAGE_APP_TOKENS permission. * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code * otherwise. */ int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options, String packageName); void addWindowToken(IBinder token, int type, int displayId); + /** + * Remove window token on a specific display. + * + * @param token Token to be removed + * @displayId The ID of the display where this token should be removed. + */ void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); @@ -735,4 +742,11 @@ interface IWindowManager * Called to show global actions. */ void showGlobalActions(); + + /** + * Sets layer tracing flags for SurfaceFlingerTrace. + * + * @param flags see definition in SurfaceTracing.cpp + */ + void setLayerTracingFlags(int flags); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 45e51f756489..a0527b7434fc 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -29,6 +29,7 @@ import android.view.IWindow; import android.view.IWindowId; import android.view.MotionEvent; import android.view.WindowManager; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; @@ -46,7 +47,12 @@ interface IWindowSession { in int viewVisibility, in int layerStackId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, - out InsetsState insetsState); + out InsetsState insetsState, out InsetsSourceControl[] activeControls); + int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs, + in int viewVisibility, in int layerStackId, in int userId, + out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, + out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, + out InsetsState insetsState, out InsetsSourceControl[] activeControls); int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, out Rect outStableInsets, out InsetsState insetsState); @@ -106,8 +112,8 @@ interface IWindowSession { out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState, out Point outSurfaceSize, - out SurfaceControl outBlastSurfaceControl); + out InsetsState insetsState, out InsetsSourceControl[] activeControls, + out Point outSurfaceSize, out SurfaceControl outBlastSurfaceControl); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 72ddaca27a76..43c7bede38c2 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -18,6 +18,7 @@ package android.view; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; +import static android.view.InsetsState.toInternalType; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.ime; @@ -471,7 +472,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!localStateChanged && mLastDispachedState.equals(state)) { return false; } - mState.set(state); + updateState(state); mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); if (localStateChanged) { @@ -480,11 +481,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { sendStateToWindowManager(); } + return true; + } + + private void updateState(InsetsState newState) { + mState.setDisplayFrame(newState.getDisplayFrame()); + for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { + InsetsSource source = newState.sourceAt(i); + getSourceConsumer(source.getType()).updateSource(source); + } + for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { + InsetsSource source = mState.sourceAt(i); + if (newState.peekSource(source.getType()) == null) { + mState.removeSource(source.getType()); + } + } if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); } - return true; } private boolean captionInsetsUnchanged() { @@ -879,8 +894,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation control.cancel(); } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - if (mRunningAnimations.get(i).runner == control) { + RunningAnimation runningAnimation = mRunningAnimations.get(i); + if (runningAnimation.runner == control) { mRunningAnimations.remove(i); + ArraySet<Integer> types = toInternalType(control.getTypes()); + for (int j = types.size() - 1; j >= 0; j--) { + if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) { + mViewRoot.notifyInsetsChanged(); + } + } break; } } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index f74221dc3e3a..6cb93746a9a4 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -16,11 +16,13 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.toPublicType; import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.Rect; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; @@ -64,6 +66,8 @@ public class InsetsSourceConsumer { private final Supplier<Transaction> mTransactionSupplier; private @Nullable InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; + private Rect mPendingFrame; + private Rect mPendingVisibleFrame; public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -87,6 +91,8 @@ public class InsetsSourceConsumer { if (mSourceControl == control) { return; } + SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; + final InsetsSourceControl lastControl = mSourceControl; mSourceControl = control; @@ -112,6 +118,12 @@ public class InsetsSourceConsumer { // However make sure that the leash visibility is still up to date. if (applyLocalVisibilityOverride()) { mController.notifyVisibilityChanged(); + } + + // If we have a new leash, make sure visibility is up-to-date, even though we + // didn't want to run an animation above. + SurfaceControl newLeash = mSourceControl.getLeash(); + if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { applyHiddenToControl(); } } @@ -215,6 +227,38 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } + void updateSource(InsetsSource newSource) { + InsetsSource source = mState.peekSource(mType); + if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE + || source.getFrame().equals(newSource.getFrame())) { + mState.addSource(newSource); + return; + } + + // Frame is changing while animating. Keep note of the new frame but keep existing frame + // until animaition is finished. + newSource = new InsetsSource(newSource); + mPendingFrame = new Rect(newSource.getFrame()); + mPendingVisibleFrame = newSource.getVisibleFrame() != null + ? new Rect(newSource.getVisibleFrame()) + : null; + newSource.setFrame(source.getFrame()); + newSource.setVisibleFrame(source.getVisibleFrame()); + mState.addSource(newSource); + } + + boolean notifyAnimationFinished() { + if (mPendingFrame != null) { + InsetsSource source = mState.getSource(mType); + source.setFrame(mPendingFrame); + source.setVisibleFrame(mPendingVisibleFrame); + mPendingFrame = null; + mPendingVisibleFrame = null; + return true; + } + return false; + } + /** * Sets requested visibility from the client, regardless of whether we are able to control it at * the moment. diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index f3ec65f997ba..e001b668f71a 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -44,8 +44,7 @@ public class InsetsSourceControl implements Parcelable { public InsetsSourceControl(InsetsSourceControl other) { mType = other.mType; if (other.mLeash != null) { - mLeash = new SurfaceControl(); - mLeash.copyFrom(other.mLeash); + mLeash = new SurfaceControl(other.mLeash); } else { mLeash = null; } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a37c1cbc76ad..1086774fc8ff 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -32,6 +32,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.ColorSpace; @@ -215,6 +216,10 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetFrameRate( long transactionObj, long nativeObject, float frameRate, int compatibility); + private static native long nativeGetHandle(long nativeObject); + + private static native long nativeAcquireFrameRateFlexibilityToken(); + private static native void nativeReleaseFrameRateFlexibilityToken(long token); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -222,6 +227,7 @@ public final class SurfaceControl implements Parcelable { * @hide */ public long mNativeObject; + private long mNativeHandle; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -424,12 +430,13 @@ public final class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } mNativeObject = nativeObject; + mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; } /** * @hide */ - public void copyFrom(SurfaceControl other) { + public void copyFrom(@NonNull SurfaceControl other) { mName = other.mName; mWidth = other.mWidth; mHeight = other.mHeight; @@ -849,23 +856,19 @@ public final class SurfaceControl implements Parcelable { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); } - + mNativeHandle = nativeGetHandle(mNativeObject); mCloseGuard.open("release"); } - /** This is a transfer constructor, useful for transferring a live SurfaceControl native - * object to another Java wrapper which could have some different behavior, e.g. - * event logging. + /** + * Copy constructor. Creates a new native object pointing to the same surface as {@code other}. + * + * @param other The object to copy the surface from. * @hide */ - public SurfaceControl(SurfaceControl other) { - mName = other.mName; - mWidth = other.mWidth; - mHeight = other.mHeight; - mNativeObject = other.mNativeObject; - other.mCloseGuard.close(); - other.mNativeObject = 0; - mCloseGuard.open("release"); + @TestApi + public SurfaceControl(@NonNull SurfaceControl other) { + copyFrom(other); } private SurfaceControl(Parcel in) { @@ -917,6 +920,18 @@ public final class SurfaceControl implements Parcelable { } /** + * Checks whether two {@link SurfaceControl} objects represent the same surface. + * + * @param other The other object to check + * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface. + * @hide + */ + @TestApi + public boolean isSameSurface(@NonNull SurfaceControl other) { + return other.mNativeHandle == mNativeHandle; + } + + /** * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link * android.view.SurfaceControlProto}. * @@ -973,6 +988,7 @@ public final class SurfaceControl implements Parcelable { if (mNativeObject != 0) { nativeRelease(mNativeObject); mNativeObject = 0; + mNativeHandle = 0; mCloseGuard.close(); } } @@ -2868,4 +2884,25 @@ public final class SurfaceControl implements Parcelable { } } } + + /** + * Acquire a frame rate flexibility token, which allows surface flinger to freely switch display + * frame rates. This is used by CTS tests to put the device in a consistent state. See + * ISurfaceComposer::acquireFrameRateFlexibilityToken(). The caller must have the + * ACCESS_SURFACE_FLINGER permission, or else the call will fail, returning 0. + * @hide + */ + @TestApi + public static long acquireFrameRateFlexibilityToken() { + return nativeAcquireFrameRateFlexibilityToken(); + } + + /** + * Release a frame rate flexibility token. + * @hide + */ + @TestApi + public static void releaseFrameRateFlexibilityToken(long token) { + nativeReleaseFrameRateFlexibilityToken(token); + } } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index fe70ff7a1dbf..174165c5ba59 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -124,8 +124,14 @@ public class SurfaceControlViewHost { /** @hide */ public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm) { + this(c, d, wwm, false /* useSfChoreographer */); + } + + /** @hide */ + public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, + @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) { mWm = wwm; - mViewRoot = new ViewRootImpl(c, d, mWm); + mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer); mViewRoot.forceDisableBLAST(); mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index c89e0c9fc60e..b677ccd9a618 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1037,7 +1037,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mTmpTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth, mSurfaceHeight); } - } else if ((layoutSizeChanged || positionChanged) && + } else if ((layoutSizeChanged || positionChanged || visibleChanged) && viewRoot.useBLAST()) { viewRoot.setUseBLASTSyncTransaction(); } @@ -1241,7 +1241,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); - if (frameNumber > 0 && viewRoot != null && !viewRoot.useBLAST()) { + if (frameNumber > 0 && viewRoot != null && !viewRoot.isDrawingToBLASTTransaction()) { t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(), frameNumber); } @@ -1258,7 +1258,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void setParentSpaceRectangle(Rect position, long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); - final boolean useBLAST = viewRoot.useBLAST(); + final boolean useBLAST = viewRoot.isDrawingToBLASTTransaction(); final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() : mRtTransaction; @@ -1319,7 +1319,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); - boolean useBLAST = viewRoot != null && viewRoot.useBLAST(); + boolean useBLAST = viewRoot != null && viewRoot.isDrawingToBLASTTransaction(); if (DEBUG) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index da186087a34a..8abe72fc91e8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14684,17 +14684,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } - if (isAccessibilityPane()) { - if (isVisible != oldVisible) { + + if (isVisible != oldVisible) { + if (isAccessibilityPane()) { notifyViewAccessibilityStateChangedIfNeeded(isVisible ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); } - } - notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); - if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) { - postUpdateSystemGestureExclusionRects(); + notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); + + if (!getSystemGestureExclusionRects().isEmpty()) { + postUpdateSystemGestureExclusionRects(); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3534bb0f763f..ed1edc3bd526 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.LAST_TYPE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -110,6 +111,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; +import android.os.UserHandle; import android.sysprop.DisplayProperties; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; @@ -449,7 +451,7 @@ public final class ViewRootImpl implements ViewParent, InputQueue mInputQueue; @UnsupportedAppUsage FallbackEventHandler mFallbackEventHandler; - Choreographer mChoreographer; + final Choreographer mChoreographer; // used in relayout to get SurfaceControl size // for BLAST adapter surface setup @@ -558,7 +560,8 @@ public final class ViewRootImpl implements ViewParent, final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeSystemBars; - private InsetsState mTempInsets = new InsetsState(); + private final InsetsState mTempInsets = new InsetsState(); + private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[LAST_TYPE + 1]; final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); @@ -692,11 +695,18 @@ public final class ViewRootImpl implements ViewParent, private SurfaceControl.Transaction mRtBLASTSyncTransaction = new SurfaceControl.Transaction(); private String mTag = TAG; + public ViewRootImpl(Context context, Display display) { - this(context, display, WindowManagerGlobal.getWindowSession()); + this(context, display, WindowManagerGlobal.getWindowSession(), + false /* useSfChoreographer */); } public ViewRootImpl(Context context, Display display, IWindowSession session) { + this(context, display, session, false /* useSfChoreographer */); + } + + public ViewRootImpl(Context context, Display display, IWindowSession session, + boolean useSfChoreographer) { mContext = context; mWindowSession = session; mDisplay = display; @@ -732,7 +742,8 @@ public final class ViewRootImpl implements ViewParent, mDensity = context.getResources().getDisplayMetrics().densityDpi; mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = new PhoneFallbackEventHandler(context); - mChoreographer = Choreographer.getInstance(); + mChoreographer = useSfChoreographer + ? Choreographer.getSfInstance() : Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mInsetsController = new InsetsController(this); @@ -883,6 +894,14 @@ public final class ViewRootImpl implements ViewParent, * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { + setView(view, attrs, panelParentView, UserHandle.myUserId()); + } + + /** + * We have one child + */ + public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, + int userId) { synchronized (this) { if (mView == null) { mView = view; @@ -991,11 +1010,11 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); adjustLayoutParamsForCompatibility(mWindowAttributes); - res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, - getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, + res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, + getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, - mTempInsets); + mTempInsets, mTempControls); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; @@ -1020,6 +1039,7 @@ public final class ViewRootImpl implements ViewParent, (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0; mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars; mInsetsController.onStateChanged(mTempInsets); + mInsetsController.onControlsChanged(mTempControls); if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -1064,6 +1084,9 @@ public final class ViewRootImpl implements ViewParent, throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); + case WindowManagerGlobal.ADD_INVALID_USER: + throw new WindowManager.BadTokenException("Unable to add Window " + + mWindow + " -- requested userId is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); @@ -3006,6 +3029,10 @@ public final class ViewRootImpl implements ViewParent, if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { reportNextDraw(); } + if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) { + reportNextDraw(); + setUseBLASTSyncTransaction(); + } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; @@ -3714,7 +3741,7 @@ public final class ViewRootImpl implements ViewParent, if (needFrameCompleteCallback) { final Handler handler = mAttachInfo.mHandler; mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { - finishBLASTSync(); + finishBLASTSync(!reportNextDraw); handler.postAtFrontOfQueue(() -> { if (reportNextDraw) { // TODO: Use the frame number @@ -3748,7 +3775,7 @@ public final class ViewRootImpl implements ViewParent, if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; - finishBLASTSync(); + finishBLASTSync(true /* apply */); } } finally { mIsDrawing = false; @@ -7348,7 +7375,7 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, - mSurfaceSize, mBlastSurfaceControl); + mTempControls, mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); @@ -7378,6 +7405,7 @@ public final class ViewRootImpl implements ViewParent, } setFrame(mTmpFrame); mInsetsController.onStateChanged(mTempInsets); + mInsetsController.onControlsChanged(mTempControls); return relayoutResult; } @@ -9564,10 +9592,15 @@ public final class ViewRootImpl implements ViewParent, mNextDrawUseBLASTSyncTransaction = true; } - private void finishBLASTSync() { + private void finishBLASTSync(boolean apply) { if (mNextReportConsumeBLAST) { mNextReportConsumeBLAST = false; - mRtBLASTSyncTransaction.apply(); + + if (apply) { + mRtBLASTSyncTransaction.apply(); + } else { + mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction); + } } } @@ -9602,4 +9635,12 @@ public final class ViewRootImpl implements ViewParent, boolean useBLAST() { return mUseBLASTAdapter; } + + /** + * Returns true if we are about to or currently processing a draw directed + * in to a BLAST transaction. + */ + boolean isDrawingToBLASTTransaction() { + return mNextReportConsumeBLAST; + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 410d9afe73da..fba6a55ef6db 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -102,6 +102,14 @@ public final class WindowManagerGlobal { public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 0x40; /** + * This flag indicates the client should not directly submit it's next frame, + * but instead should pass it in the postDrawTransaction of + * {@link WindowManagerService#finishDrawing}. This is used by the WM + * BLASTSyncEngine to synchronize rendering of multiple windows. + */ + public static final int RELAYOUT_RES_BLAST_SYNC = 0x80; + + /** * Flag for relayout: the client will be later giving * internal insets; as a result, the window will not impact other window * layouts until the insets are given. @@ -136,6 +144,7 @@ public final class WindowManagerGlobal { public static final int ADD_PERMISSION_DENIED = -8; public static final int ADD_INVALID_DISPLAY = -9; public static final int ADD_INVALID_TYPE = -10; + public static final int ADD_INVALID_USER = -11; @UnsupportedAppUsage private static WindowManagerGlobal sDefaultWindowManager; @@ -317,7 +326,7 @@ public final class WindowManagerGlobal { } public void addView(View view, ViewGroup.LayoutParams params, - Display display, Window parentWindow) { + Display display, Window parentWindow, int userId) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } @@ -394,7 +403,7 @@ public final class WindowManagerGlobal { // do this last because it fires off messages to start doing things try { - root.setView(view, wparams, panelParentView); + root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 316a5f2c88d2..2975d5ee8e1c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -104,7 +104,8 @@ public final class WindowManagerImpl implements WindowManager { @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); - mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow); + mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, + mContext.getUserId()); } @Override diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 144f8e3a7108..ec5130143086 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -104,7 +104,7 @@ public class WindowlessWindowManager implements IWindowSession { int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession) .setParent(mRootSurface) .setFormat(attrs.format) @@ -130,6 +130,20 @@ public class WindowlessWindowManager implements IWindowSession { return WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } + /** + * IWindowSession implementation. Currently this class doesn't need to support for multi-user. + */ + @Override + public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, int userId, Rect outFrame, + Rect outContentInsets, Rect outStableInsets, + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { + return addToDisplay(window, seq, attrs, viewVisibility, displayId, + outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, + outInsetsState, outActiveControls); + } + @Override public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq, android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId, @@ -179,7 +193,8 @@ public class WindowlessWindowManager implements IWindowSession { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { final State state; synchronized (this) { state = mStateForWindow.get(window.asBinder()); diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index dc87453bd867..6a109253a27c 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1275,7 +1275,14 @@ public final class AccessibilityManager { /** * Register the provided {@link RemoteAction} with the given actionId - * + * <p> + * To perform established system actions, an accessibility service uses the GLOBAL_ACTION + * constants in {@link android.accessibilityservice.AccessibilityService}. To provide a + * customized implementation for one of these actions, the id of the registered system action + * must match that of the corresponding GLOBAL_ACTION constant. For example, to register a + * Back action, {@code actionId} must be + * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK} + * </p> * @param action The remote action to be registered with the given actionId as system action. * @param actionId The id uniquely identify the system action. * @hide diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index edc6b1251f2c..4519aefe65fe 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -832,7 +832,7 @@ public final class ContentCaptureManager { void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, Executor executor) { mWriteAdapterHardReferences.put(delegate, adapter); - mExecutorHardReferences.remove(delegate, executor); + mExecutorHardReferences.put(delegate, executor); } Executor getExecutor(DataShareAdapterDelegate delegate) { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 482d5b25e9da..71dd6653f6a6 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1968,6 +1968,38 @@ public final class InputMethodManager { return true; } + /** + * An empty method only to avoid crashes of apps that call this method via reflection and do not + * handle {@link NoSuchMethodException} in a graceful manner. + * + * @deprecated This is an empty method. No framework method must call this method. + * @hide + */ + @Deprecated + @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "{@code androidx.activity.ComponentActivity}") + public void windowDismissed(IBinder appWindowToken) { + // Intentionally empty. + // + // It seems that some applications call this method via reflection to null clear the + // following fields that used to exist in InputMethodManager: + // * InputMethodManager#mCurRootView + // * InputMethodManager#mServedView + // * InputMethodManager#mNextServedView + // so that these objects can be garbage-collected when an Activity gets dismissed. + // + // It is indeed true that older versions of InputMethodManager had issues that prevented + // these fields from being null-cleared when it should have been, but the understanding of + // the engineering team is that all known issues have already been fixed as of Android 10. + // + // For older devices, developers can work around the object leaks by using + // androidx.activity.ComponentActivity. + // See https://issuetracker.google.com/u/1/issues/37122102 for details. + // + // If you believe InputMethodManager is leaking objects in API 24 or any later version, + // please file a bug at https://issuetracker.google.com/issues/new?component=192705. + } + private int getStartInputFlags(View focusedView, int startInputFlags) { startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; if (focusedView.onCheckIsTextEditor()) { diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 842ba2975b3b..6ad5cb913553 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -27,8 +27,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.SpannedString; -import com.android.internal.util.Preconditions; - import java.lang.annotation.Retention; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -491,7 +489,11 @@ public final class ConversationActions implements Parcelable { */ @NonNull public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) { - mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions); + if (maxSuggestions < -1) { + throw new IllegalArgumentException("maxSuggestions has to be greater than or " + + "equal to -1."); + } + mMaxSuggestions = maxSuggestions; return this; } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 71ccb595278b..849488d42bcf 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -484,21 +485,21 @@ public class RadioGroup extends LinearLayout { super.onInitializeAccessibilityNodeInfo(info); if (this.getOrientation() == HORIZONTAL) { info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1, - getVisibleChildCount(), false, + getVisibleChildWithTextCount(), false, AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE)); } else { info.setCollectionInfo( - AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(), + AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildWithTextCount(), 1, false, AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE)); } } - private int getVisibleChildCount() { + private int getVisibleChildWithTextCount() { int count = 0; for (int i = 0; i < getChildCount(); i++) { if (this.getChildAt(i) instanceof RadioButton) { - if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) { + if (isVisibleWithText((RadioButton) this.getChildAt(i))) { count++; } } @@ -513,15 +514,19 @@ public class RadioGroup extends LinearLayout { int index = 0; for (int i = 0; i < getChildCount(); i++) { if (this.getChildAt(i) instanceof RadioButton) { - RadioButton radioButton = (RadioButton) this.getChildAt(i); - if (radioButton == child) { + RadioButton button = (RadioButton) this.getChildAt(i); + if (button == child) { return index; } - if (radioButton.getVisibility() == VISIBLE) { + if (isVisibleWithText(button)) { index++; } } } return -1; } + + private boolean isVisibleWithText(RadioButton button) { + return button.getVisibility() == VISIBLE && !TextUtils.isEmpty(button.getText()); + } }
\ No newline at end of file diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 08b32930971a..fb962103990c 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -43,7 +43,7 @@ import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; import com.android.internal.annotations.GuardedBy; @@ -610,10 +610,10 @@ public class Toast { */ TN(Context context, String packageName, Binder token, List<Callback> callbacks, @Nullable Looper looper) { - WindowManager windowManager = context.getSystemService(WindowManager.class); - AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context); - mPresenter = new ToastPresenter(context, windowManager, accessibilityManager, - getService(), packageName); + IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface( + ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); + mPresenter = new ToastPresenter(context, accessibilityManager, getService(), + packageName); mParams = mPresenter.getLayoutParams(); mPackageName = packageName; mToken = token; diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index e9d4aa668891..2679c69be4f6 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -27,6 +27,7 @@ import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -34,8 +35,10 @@ import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; /** @@ -49,12 +52,14 @@ public class ToastPresenter { private static final long SHORT_DURATION_TIMEOUT = 4000; private static final long LONG_DURATION_TIMEOUT = 7000; + @VisibleForTesting + public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification; + /** * Returns the default text toast view for message {@code text}. */ public static View getTextToastView(Context context, CharSequence text) { - View view = LayoutInflater.from(context).inflate( - R.layout.transient_notification, null); + View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null); TextView textView = view.findViewById(com.android.internal.R.id.message); textView.setText(text); return view; @@ -70,15 +75,23 @@ public class ToastPresenter { @Nullable private View mView; @Nullable private IBinder mToken; - public ToastPresenter(Context context, WindowManager windowManager, - AccessibilityManager accessibilityManager, + public ToastPresenter(Context context, IAccessibilityManager accessibilityManager, INotificationManager notificationManager, String packageName) { mContext = context; mResources = context.getResources(); - mWindowManager = windowManager; - mAccessibilityManager = accessibilityManager; + mWindowManager = context.getSystemService(WindowManager.class); mNotificationManager = notificationManager; mPackageName = packageName; + + // We obtain AccessibilityManager manually via its constructor instead of using method + // AccessibilityManager.getInstance() for 2 reasons: + // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior. + // 2. getInstance() caches the instance for the process even if we pass a different + // context to it. This is problematic for multi-user because callers can pass a context + // created via Context.createContextAsUser(). + mAccessibilityManager = new AccessibilityManager(context, accessibilityManager, + UserHandle.getCallingUserId()); + mParams = createLayoutParams(); } diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index eee222b9bf4c..6ae70b779960 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -33,8 +33,8 @@ public class DisplayAreaOrganizer extends WindowOrganizer { public static final int FEATURE_SYSTEM_FIRST = 0; // The Root display area on a display public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST; - // Display area hosting the task container. - public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1; + // Display area hosting the default task container. + public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1; // Display area hosting non-activity window tokens. public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2; diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index 1afbfeb96fc3..0f26d5d00f6d 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -16,7 +16,6 @@ package android.window; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; @@ -247,7 +246,6 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { protected ActivityOptions prepareActivityOptions(ActivityOptions options) { options = super.prepareActivityOptions(options); options.setLaunchDisplayId(getDisplayId()); - options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); return options; } diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 9fc0da83c504..d43333e507a6 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -55,6 +55,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private static final String TAG = "AbstractMultiProfilePagerAdapter"; static final int PROFILE_PERSONAL = 0; static final int PROFILE_WORK = 1; + @IntDef({PROFILE_PERSONAL, PROFILE_WORK}) @interface Profile {} @@ -365,7 +366,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { UserHandle listUserHandle = listAdapter.getUserHandle(); if (!listUserHandle.equals(mWorkProfileUserHandle) || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle) - || !hasResolvedAppsInWorkProfile(listAdapter)) { + || listAdapter.getCount() == 0) { return false; } DevicePolicyEventLogger @@ -382,20 +383,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return true; } - /** - * Returns {@code true} if there is at least one app resolved in the work profile, - * regardless of whether the work profile is enabled or not. - */ - private boolean hasResolvedAppsInWorkProfile(ResolverListAdapter listAdapter) { - List<ResolverActivity.ResolvedComponentInfo> userStateIndependentWorkResolvers = - listAdapter.mResolverListController.getUserStateIndependentResolversAsUser( - listAdapter.getIntents(), mWorkProfileUserHandle); - return userStateIndependentWorkResolvers.stream() - .anyMatch(resolvedComponentInfo -> - resolvedComponentInfo.getResolveInfoAt(0).targetUserId - == UserHandle.USER_CURRENT); - } - private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) { UserHandle listUserHandle = listAdapter.getUserHandle(); if (mWorkProfileUserHandle != null @@ -529,6 +516,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return false; } + boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) { + int count = listAdapter.getUnfilteredCount(); + return (count == 0 && listAdapter.getPlaceholderCount() == 0) + || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle) + && isQuietModeEnabled(mWorkProfileUserHandle)); + } + protected class ProfileDescriptor { final ViewGroup rootView; private final ViewGroup mEmptyStateView; diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 553721d75fd6..be43e82c3f88 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -286,6 +286,7 @@ public class AlertController { if (mTitleView != null) { mTitleView.setText(title); } + mWindow.setTitle(title); } /** diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java index dc482443040a..088973cde3e9 100644 --- a/core/java/com/android/internal/app/ChooserActivityLogger.java +++ b/core/java/com/android/internal/app/ChooserActivityLogger.java @@ -191,6 +191,9 @@ public interface ChooserActivityLogger { * ChooserActivity. */ default int typeFromIntentString(String intent) { + if (intent == null) { + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT; + } switch (intent) { case Intent.ACTION_VIEW: return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 35253b68aac7..fc3f20f7a556 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -994,7 +994,7 @@ public class ResolverActivity extends Activity implements if (isAutolaunching() || maybeAutolaunchActivity()) { return; } - if (isResolverListEmpty(listAdapter)) { + if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) { mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter); } else { mMultiProfilePagerAdapter.showListView(listAdapter); @@ -1641,16 +1641,11 @@ public class ResolverActivity extends Activity implements private void setupViewVisibilities() { ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); - if (!isResolverListEmpty(activeListAdapter)) { + if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) { addUseDifferentAppLabelIfNecessary(activeListAdapter); } } - private boolean isResolverListEmpty(ResolverListAdapter listAdapter) { - int count = listAdapter.getUnfilteredCount(); - return count == 0 && listAdapter.getPlaceholderCount() == 0; - } - /** * Add a label to signify that the user can pick a different app. * @param adapter The adapter used to provide data to item views. diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 3f897a5f26bf..033ac72dda4e 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -121,23 +121,13 @@ public class ResolverListController { List<Intent> intents, UserHandle userHandle) { int baseFlags = PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); } - /** - * Returns a list of resolved intents which is user state-independent. This means it will - * return the same results regardless of whether the {@code userHandle} user is disabled or not. - */ - public List<ResolverActivity.ResolvedComponentInfo> getUserStateIndependentResolversAsUser( - List<Intent> intents, - UserHandle userHandle) { - int baseFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); - } - private List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUserInternal( List<Intent> intents, UserHandle userHandle, diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index ff03f1a1a2ab..505a05eb9c23 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -205,9 +205,15 @@ public final class Zygote { /** List of packages with the same uid, and its app data info: volume uuid and inode. */ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map"; + /** List of whitelisted packages and its app data info: volume uuid and inode. */ + public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map"; + /** Bind mount app storage dirs to lower fs not via fuse */ public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs"; + /** Bind mount app storage dirs to lower fs not via fuse */ + public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs"; + /** * An extraArg passed when a zygote process is forking a child-zygote, specifying a name * in the abstract socket namespace. This socket name is what the new child zygote @@ -313,6 +319,8 @@ public final class Zygote { * @param isTopApp true if the process is for top (high priority) application. * @param pkgDataInfoList A list that stores related packages and its app data * info: volume uuid and inode. + * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps. + * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. * * @return 0 if this is the child, pid of the child @@ -321,13 +329,15 @@ public final class Zygote { static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) { + boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { ZygoteHooks.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, - pkgDataInfoList, bindMountAppStorageDirs); + pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs, + bindMountAppStorageDirs); if (pid == 0) { // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); @@ -344,6 +354,7 @@ public final class Zygote { int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, String[] pkgDataInfoList, + String[] whitelistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs); /** @@ -371,15 +382,19 @@ public final class Zygote { * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name, * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid, * app_b_ce_inode, ...]; + * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps. + * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. */ private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, - String[] pkgDataInfoList, boolean bindMountAppStorageDirs) { + String[] pkgDataInfoList, String[] whitelistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, startChildZygote, instructionSet, appDataDir, isTopApp, - pkgDataInfoList, bindMountAppStorageDirs); + pkgDataInfoList, whitelistedDataInfoList, + bindMountAppDataDirs, bindMountAppStorageDirs); // Note that this event ends at the end of handleChildProc. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); @@ -399,7 +414,8 @@ public final class Zygote { private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, - String[] pkgDataInfoList, boolean bindMountAppStorageDirs); + String[] pkgDataInfoList, String[] whitelistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs); /** * Called to do any initialization before starting an application. @@ -724,7 +740,8 @@ public final class Zygote { args.mRuntimeFlags, rlimits, args.mMountExternal, args.mSeInfo, args.mNiceName, args.mStartChildZygote, args.mInstructionSet, args.mAppDataDir, args.mIsTopApp, - args.mPkgDataInfoList, args.mBindMountAppStorageDirs); + args.mPkgDataInfoList, args.mWhitelistedDataInfoList, + args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -1060,4 +1077,11 @@ public final class Zygote { */ @FastNative public static native int nativeParseSigChld(byte[] in, int length, int[] out); + + /** + * Returns whether the kernel supports tagged pointers. Present in the + * Android Common Kernel from 4.14 and up. By default, you should prefer + * fully-feature Memory Tagging, rather than the static Tagged Pointers. + */ + public static native boolean nativeSupportsTaggedPointers(); } diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 1a63765fcaa6..94c1f71a26db 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -227,11 +227,22 @@ class ZygoteArguments { String[] mPkgDataInfoList; /** + * A list that stores all whitelisted app data info: volume uuid and inode. + * Null if it does need to do app data isolation. + */ + String[] mWhitelistedDataInfoList; + + /** * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS */ boolean mBindMountAppStorageDirs; /** + * @see Zygote#BIND_MOUNT_APP_DATA_DIRS + */ + boolean mBindMountAppDataDirs; + + /** * Constructs instance and parses args * * @param args zygote command-line args @@ -452,8 +463,12 @@ class ZygoteArguments { } } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) { mPkgDataInfoList = getAssignmentList(arg); + } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) { + mWhitelistedDataInfoList = getAssignmentList(arg); } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) { mBindMountAppStorageDirs = true; + } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) { + mBindMountAppDataDirs = true; } else { break; } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index bc8dfd4aa402..e6a3029c5b2b 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -258,7 +258,8 @@ class ZygoteConnection { parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp, - parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs); + parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList, + parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs); try { if (pid == 0) { diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index ec1f516df5f3..c2b13c971020 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -757,9 +757,11 @@ public class ZygoteInit { Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); - /* Enable pointer tagging in the system server unconditionally. Hardware support for - * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */ - parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + if (Zygote.nativeSupportsTaggedPointers()) { + /* Enable pointer tagging in the system server. Hardware support for this is present + * in all ARMv8 CPUs. */ + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } /* Enable gwp-asan on the system server with a small probability. This is the same * policy as applied to native processes and system apps. */ diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 71cf5cad316e..b6c58e16b51c 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -68,7 +68,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.LayerDrawable; -import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -120,7 +119,6 @@ import com.android.internal.widget.DecorCaptionView; import com.android.internal.widget.FloatingToolbar; import java.util.List; -import java.util.function.Function; /** @hide */ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { @@ -283,11 +281,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Insets mLastBackgroundInsets = Insets.NONE; private boolean mDrawLegacyNavigationBarBackground; - /** - * Whether the app targets an SDK that uses the new insets APIs. - */ - private boolean mUseNewInsetsApi; - private PendingInsetsController mPendingInsetsController = new PendingInsetsController(); DecorView(Context context, int featureId, PhoneWindow window, @@ -319,7 +312,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); - mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R; } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1189,23 +1181,23 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // these flags wouldn't make the window draw behind the navigation bar, unless // LAYOUT_HIDE_NAVIGATION was set. // - // Note: Once the app targets R+, we no longer do this logic because we can't rely on - // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION to indicate whether the app wants to handle it by - // themselves. + // Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer + // consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION. boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR)); + boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows; boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 + && decorFitsSystemWindows && !hideNavigation) || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 - && !hideNavigation - // TODO IME wrap_content windows need to have margin to work properly - && (!mUseNewInsetsApi || isImeWindow)) + && decorFitsSystemWindows + && !hideNavigation) || forceConsumingNavBar; // If we didn't request fullscreen layout, but we still got it because of the @@ -1216,6 +1208,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind || (attrs.flags & FLAG_FULLSCREEN) != 0 || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR)); boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 + && decorFitsSystemWindows && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsBarBackgrounds diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 138d0dd39537..25c114f4b7c1 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -343,8 +343,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** @see ViewRootImpl#mActivityConfigCallback */ private ActivityConfigCallback mActivityConfigCallback; - private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener = - sDefaultContentInsetsApplier; + boolean mDecorFitsSystemWindows = true; static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( @@ -2138,9 +2137,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ void onViewRootImplSet(ViewRootImpl viewRoot) { viewRoot.setActivityConfigCallback(mActivityConfigCallback); - viewRoot.setOnContentApplyWindowInsetsListener( - mPendingOnContentApplyWindowInsetsListener); - mPendingOnContentApplyWindowInsetsListener = null; + applyDecorFitsSystemWindows(); } static private final String FOCUSED_ID_TAG = "android:focusedViewId"; @@ -3907,14 +3904,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { + mDecorFitsSystemWindows = decorFitsSystemWindows; + applyDecorFitsSystemWindows(); + } + + private void applyDecorFitsSystemWindows() { ViewRootImpl impl = getViewRootImplOrNull(); - OnContentApplyWindowInsetsListener listener = decorFitsSystemWindows - ? sDefaultContentInsetsApplier - : null; if (impl != null) { - impl.setOnContentApplyWindowInsetsListener(listener); - } else { - mPendingOnContentApplyWindowInsetsListener = listener; + impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows + ? sDefaultContentInsetsApplier + : null); } } } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 5b79b184b6a4..38f5f3279c8e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -204,8 +204,8 @@ oneway interface IStatusBar /** * Displays a text toast. */ - void showToast(String packageName, IBinder token, CharSequence text, IBinder windowToken, - int duration, @nullable ITransientNotificationCallback callback); + void showToast(int uid, String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, @nullable ITransientNotificationCallback callback); /** * Cancels toast with token {@code token} in {@code packageName}. diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java index 3c9791791a68..9168438dc2bf 100644 --- a/core/java/com/android/internal/util/FunctionalUtils.java +++ b/core/java/com/android/internal/util/FunctionalUtils.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.annotation.NonNull; import android.os.RemoteException; import android.util.ExceptionUtils; @@ -218,4 +219,37 @@ public class FunctionalUtils { } } } + + // TODO: add unit test + /** + * Gets a user-friendly name for a lambda function. + */ + @NonNull + public static String getLambdaName(@NonNull Object function) { + // Full function has one of the following formats: + // package-$$Lambda$class$randomId + // package-$$Lambda$randomId + // + // We just want just package.class$Lambda (or package$Lambda) respectively + + final String fullFunction = function.toString(); + + final int endPkgIdx = fullFunction.indexOf("-$$"); + if (endPkgIdx == -1) return fullFunction; + + // firstDollarIdx could be either beginning of class or beginning of the random id + final int firstDollarIdx = fullFunction.indexOf('$', endPkgIdx + 3); + if (firstDollarIdx == -1) return fullFunction; + + final int endClassIdx = fullFunction.indexOf('$', firstDollarIdx + 1); + if (endClassIdx == -1) { + // Just package + return fullFunction.substring(0, endPkgIdx - 1) + "$Lambda"; + } + + // Package + class + return fullFunction.substring(0, endPkgIdx) + + fullFunction.substring(firstDollarIdx + 1, endClassIdx) + + "$Lambda"; + } } diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java index e4a44084e91c..1646a07b8001 100755 --- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java +++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java @@ -16,7 +16,6 @@ package com.android.internal.util.function.pooled; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Message; import android.text.TextUtils; @@ -25,6 +24,7 @@ import android.util.Pools; import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; +import com.android.internal.util.FunctionalUtils; import com.android.internal.util.function.DecConsumer; import com.android.internal.util.function.DecFunction; import com.android.internal.util.function.DecPredicate; @@ -580,36 +580,6 @@ final class PooledLambdaImpl<R> extends OmniFunction<Object, return r; } - // TODO: add unit test - @NonNull - private static String getFriendlyName(@NonNull Object function) { - // Full function has one of the following formats: - // package-$$Lambda$class$randomId - // package-$$Lambda$randomId - // - // We just want just package.class$Lambda (or package$Lambda) respectively - - final String fullFunction = function.toString(); - - final int endPkgIdx = fullFunction.indexOf("-$$"); - if (endPkgIdx == -1) return fullFunction; - - // firstDollarIdx could be either beginning of class or beginning of the random id - final int firstDollarIdx = fullFunction.indexOf('$', endPkgIdx + 3); - if (firstDollarIdx == -1) return fullFunction; - - final int endClassIdx = fullFunction.indexOf('$', firstDollarIdx + 1); - if (endClassIdx == -1) { - // Just package - return fullFunction.substring(0, endPkgIdx - 1) + "$Lambda"; - } - - // Package + class - return fullFunction.substring(0, endPkgIdx) - + fullFunction.substring(firstDollarIdx + 1, endClassIdx) - + "$Lambda"; - } - private static void setIfInBounds(Object[] array, int i, Object a) { if (i < ArrayUtils.size(array)) array[i] = a; } @@ -651,7 +621,7 @@ final class PooledLambdaImpl<R> extends OmniFunction<Object, @Override public String getTraceName() { - return getFriendlyName(mFunc); + return FunctionalUtils.getLambdaName(mFunc); } private boolean isRecycled() { diff --git a/core/java/com/android/internal/view/SurfaceFlingerVsyncChoreographer.java b/core/java/com/android/internal/view/SurfaceFlingerVsyncChoreographer.java deleted file mode 100644 index 924b3f704f5b..000000000000 --- a/core/java/com/android/internal/view/SurfaceFlingerVsyncChoreographer.java +++ /dev/null @@ -1,94 +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 - */ - -package com.android.internal.view; - -import android.os.Handler; -import android.os.Message; -import android.view.Choreographer; -import android.view.Display; - -/** - * Utility class to schedule things at vsync-sf instead of vsync-app - * @hide - */ -public class SurfaceFlingerVsyncChoreographer { - - private static final long ONE_MS_IN_NS = 1000000; - private static final long ONE_S_IN_NS = ONE_MS_IN_NS * 1000; - - private final Handler mHandler; - private final Choreographer mChoreographer; - - /** - * The offset between vsync-app and vsync-surfaceflinger. See - * {@link #calculateAppSurfaceFlingerVsyncOffsetMs} why this is necessary. - */ - private long mSurfaceFlingerOffsetMs; - - public SurfaceFlingerVsyncChoreographer(Handler handler, Display display, - Choreographer choreographer) { - mHandler = handler; - mChoreographer = choreographer; - mSurfaceFlingerOffsetMs = calculateAppSurfaceFlingerVsyncOffsetMs(display); - } - - public long getSurfaceFlingerOffsetMs() { - return mSurfaceFlingerOffsetMs; - } - - /** - * This method calculates the offset between vsync-surfaceflinger and vsync-app. If vsync-app - * is a couple of milliseconds before vsync-sf, a touch or animation event that causes a surface - * flinger transaction are sometimes processed before the vsync-sf tick, and sometimes after, - * which leads to jank. Figure out this difference here and then post all the touch/animation - * events to start being processed at vsync-sf. - * - * @return The offset between vsync-app and vsync-sf, or 0 if vsync app happens after vsync-sf. - */ - private long calculateAppSurfaceFlingerVsyncOffsetMs(Display display) { - - // Calculate vsync offset from SurfaceFlinger. - // See frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:getDisplayConfigs - long vsyncPeriod = (long) (ONE_S_IN_NS / display.getRefreshRate()); - long sfVsyncOffset = vsyncPeriod - (display.getPresentationDeadlineNanos() - ONE_MS_IN_NS); - return Math.max(0, (sfVsyncOffset - display.getAppVsyncOffsetNanos()) / ONE_MS_IN_NS); - } - - public void scheduleAtSfVsync(Runnable r) { - final long delay = calculateDelay(); - if (delay <= 0) { - r.run(); - } else { - mHandler.postDelayed(r, delay); - } - } - - public void scheduleAtSfVsync(Handler h, Message m) { - final long delay = calculateDelay(); - if (delay <= 0) { - h.handleMessage(m); - } else { - m.setAsynchronous(true); - h.sendMessageDelayed(m, delay); - } - } - - private long calculateDelay() { - final long sinceFrameStart = System.nanoTime() - mChoreographer.getLastFrameTimeNanos(); - return mSurfaceFlingerOffsetMs - sinceFrameStart / 1000000; - } -} diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 523c7493420b..26684201019c 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -40,7 +40,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; @@ -121,6 +120,7 @@ public class ConversationLayout extends FrameLayout private CachingIconView mConversationIconBadgeBg; private Icon mLargeIcon; private View mExpandButtonContainer; + private View mExpandButtonInnerContainer; private ViewGroup mExpandButtonAndContentContainer; private NotificationExpandButton mExpandButton; private MessagingLinearLayout mImageMessageContainer; @@ -242,6 +242,7 @@ public class ConversationLayout extends FrameLayout mConversationHeader = findViewById(R.id.conversation_header); mContentContainer = findViewById(R.id.notification_action_list_margin_target); mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container); + mExpandButtonInnerContainer = findViewById(R.id.expand_button_inner_container); mExpandButton = findViewById(R.id.expand_button); mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize( R.dimen.conversation_expand_button_top_margin_expanded); @@ -385,14 +386,17 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE); - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + boolean visible = mIsCollapsed && unreadCount > 1; + mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); + if (visible) { + CharSequence text = unreadCount >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", unreadCount); + mUnreadBadge.setText(text); + mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); + boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; + mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + } } private void addRemoteInputHistoryToMessages( @@ -535,37 +539,26 @@ public class ConversationLayout extends FrameLayout } private void updateImageMessages() { - boolean displayExternalImage = false; - ArraySet<View> newMessages = new ArraySet<>(); - if (mIsCollapsed) { - - // When collapsed, we're displaying all image messages in a dedicated container - // on the right of the layout instead of inline. Let's add all isolated images there - int imageIndex = 0; - for (int i = 0; i < mGroups.size(); i++) { - MessagingGroup messagingGroup = mGroups.get(i); - MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); - if (isolatedMessage != null) { - newMessages.add(isolatedMessage.getView()); - displayExternalImage = true; - if (imageIndex - != mImageMessageContainer.indexOfChild(isolatedMessage.getView())) { - mImageMessageContainer.removeView(isolatedMessage.getView()); - mImageMessageContainer.addView(isolatedMessage.getView(), imageIndex); - } - imageIndex++; - } + View newMessage = null; + if (mIsCollapsed && mGroups.size() > 0) { + + // When collapsed, we're displaying the image message in a dedicated container + // on the right of the layout instead of inline. Let's add the isolated image there + MessagingGroup messagingGroup = mGroups.get(mGroups.size() -1); + MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + newMessage = isolatedMessage.getView(); } } // Remove all messages that don't belong into the image layout - for (int i = 0; i < mImageMessageContainer.getChildCount(); i++) { - View child = mImageMessageContainer.getChildAt(i); - if (!newMessages.contains(child)) { - mImageMessageContainer.removeView(child); - i--; + View previousMessage = mImageMessageContainer.getChildAt(0); + if (previousMessage != newMessage) { + mImageMessageContainer.removeView(previousMessage); + if (newMessage != null) { + mImageMessageContainer.addView(newMessage); } } - mImageMessageContainer.setVisibility(displayExternalImage ? VISIBLE : GONE); + mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE); } private void bindFacePile() { @@ -1169,7 +1162,7 @@ public class ConversationLayout extends FrameLayout layoutParams.topMargin = topMargin; mExpandButton.setLayoutParams(layoutParams); - mExpandButtonContainer.setContentDescription(mContext.getText(contentDescriptionId)); + mExpandButtonInnerContainer.setContentDescription(mContext.getText(contentDescriptionId)); } private void updateContentEndPaddings() { @@ -1213,7 +1206,7 @@ public class ConversationLayout extends FrameLayout mExpandable = expandable; if (expandable) { mExpandButtonContainer.setVisibility(VISIBLE); - mExpandButtonContainer.setOnClickListener(onClickListener); + mExpandButtonInnerContainer.setOnClickListener(onClickListener); } else { // TODO: handle content paddings to end of layout mExpandButtonContainer.setVisibility(GONE); diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index d40924b603a5..21ca948fa89c 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -229,6 +229,7 @@ public class SystemConfig { private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>(); private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>(); + private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>(); /** * Map of system pre-defined, uniquely named actors; keys are namespace, @@ -394,6 +395,10 @@ public class SystemConfig { return mRollbackWhitelistedPackages; } + public Set<String> getWhitelistedStagedInstallers() { + return mWhitelistedStagedInstallers; + } + public ArraySet<String> getAppDataIsolationWhitelistedApps() { return mAppDataIsolationWhitelistedApps; } @@ -1137,6 +1142,20 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "whitelisted-staged-installer": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mWhitelistedStagedInstallers.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; default: { Slog.w(TAG, "Tag " + name + " is unknown in " + permFile + " at " + parser.getPositionDescription()); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b2ca0a7bcbe3..1cfa12df32ab 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -29,11 +29,13 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> +#include <private/gui/ComposerService.h> #include <stdio.h> #include <system/graphics.h> #include <ui/ConfigStoreTypes.h> @@ -624,6 +626,23 @@ static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setFrameRate(ctrl, frameRate, static_cast<int8_t>(compatibility)); } +static jlong nativeAcquireFrameRateFlexibilityToken(JNIEnv* env, jclass clazz) { + sp<ISurfaceComposer> composer = ComposerService::getComposerService(); + sp<IBinder> token; + status_t result = composer->acquireFrameRateFlexibilityToken(&token); + if (result < 0) { + ALOGE("Failed acquiring frame rate flexibility token: %s (%d)", strerror(-result), result); + return 0; + } + token->incStrong((void*)nativeAcquireFrameRateFlexibilityToken); + return reinterpret_cast<jlong>(token.get()); +} + +static void nativeReleaseFrameRateFlexibilityToken(JNIEnv* env, jclass clazz, jlong tokenLong) { + sp<IBinder> token(reinterpret_cast<IBinder*>(tokenLong)); + token->decStrong((void*)nativeAcquireFrameRateFlexibilityToken); +} + static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) { const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds(); jlongArray array = env->NewLongArray(displayIds.size()); @@ -1411,6 +1430,12 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius); } + +static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { + SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject); + return reinterpret_cast<jlong>(surfaceControl->getHandle().get()); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { @@ -1474,6 +1499,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetShadowRadius }, {"nativeSetFrameRate", "(JJFI)V", (void*)nativeSetFrameRate }, + {"nativeAcquireFrameRateFlexibilityToken", "()J", + (void*)nativeAcquireFrameRateFlexibilityToken }, + {"nativeReleaseFrameRateFlexibilityToken", "(J)V", + (void*)nativeReleaseFrameRateFlexibilityToken }, {"nativeGetPhysicalDisplayIds", "()[J", (void*)nativeGetPhysicalDisplayIds }, {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;", @@ -1583,6 +1612,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeMirrorSurface }, {"nativeSetGlobalShadowSettings", "([F[FFFF)V", (void*)nativeSetGlobalShadowSettings }, + {"nativeGetHandle", "(J)J", + (void*)nativeGetHandle }, }; int register_android_view_SurfaceControl(JNIEnv* env) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index ea3c0fa9fc3c..924dc4b3a051 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -110,7 +110,6 @@ using android::base::StringAppendF; using android::base::StringPrintf; using android::base::WriteStringToFile; using android::base::GetBoolProperty; -using android::base::GetProperty; #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ append(StringPrintf(__VA_ARGS__)) @@ -170,18 +169,6 @@ static int gSystemServerSocketFd = -1; static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751; -/** - * Property to control if app data isolation is enabled. - */ -static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = - "persist.zygote.app_data_isolation"; - -/** - * Property to enable app data isolation for sdcard obb or data in vold. - */ -static const std::string ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = - "persist.sys.vold_app_data_isolation_enabled"; - static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000; static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF; @@ -1319,20 +1306,13 @@ static void relabelAllDirs(const char* path, security_context_t context, fail_fn * be decrypted after storage is decrypted. * */ -static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, - uid_t uid, const char* process_name, jstring managed_nice_name, - fail_fn_t fail_fn) { +static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_data_info_list, + uid_t uid, const char* process_name, + jstring managed_nice_name, fail_fn_t fail_fn) { const userid_t userId = multiuser_get_user_id(uid); - auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); - - int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0; - // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode> - if ((size % 3) != 0) { - fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size)); - } - ensureInAppMountNamespace(fail_fn); + int size = merged_data_info_list.size(); // Mount tmpfs on all possible data directories, so app no longer see the original apps data. char internalCePath[PATH_MAX]; @@ -1377,14 +1357,10 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, bool legacySymlinkCreated = false; for (int i = 0; i < size; i += 3) { - jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); - std::string packageName = extract_fn(package_str).value(); + std::string const & packageName = merged_data_info_list[i]; + std::string const & volUuid = merged_data_info_list[i + 1]; + std::string const & inode = merged_data_info_list[i + 2]; - jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1)); - std::string volUuid = extract_fn(vol_str).value(); - - jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2)); - std::string inode = extract_fn(inode_str).value(); std::string::size_type sz; long long ceDataInode = std::stoll(inode, &sz); @@ -1482,6 +1458,48 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, freecon(dataDataContext); } +static void insertPackagesToMergedList(JNIEnv* env, + std::vector<std::string>& merged_data_info_list, + jobjectArray data_info_list, const char* process_name, + jstring managed_nice_name, fail_fn_t fail_fn) { + + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); + + int size = (data_info_list != nullptr) ? env->GetArrayLength(data_info_list) : 0; + // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode> + if ((size % 3) != 0) { + fail_fn(CREATE_ERROR("Wrong data_info_list size %d", size)); + } + + for (int i = 0; i < size; i += 3) { + jstring package_str = (jstring) (env->GetObjectArrayElement(data_info_list, i)); + std::string packageName = extract_fn(package_str).value(); + merged_data_info_list.push_back(packageName); + + jstring vol_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 1)); + std::string volUuid = extract_fn(vol_str).value(); + merged_data_info_list.push_back(volUuid); + + jstring inode_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 2)); + std::string inode = extract_fn(inode_str).value(); + merged_data_info_list.push_back(inode); + } +} + +static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, + jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name, + jstring managed_nice_name, fail_fn_t fail_fn) { + + ensureInAppMountNamespace(fail_fn); + std::vector<std::string> merged_data_info_list; + insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list, + process_name, managed_nice_name, fail_fn); + insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list, + process_name, managed_nice_name, fail_fn); + + isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn); +} + /** * Like isolateAppData(), isolate jit profile directories, so apps don't see what * other apps are installed by reading content inside /data/misc/profiles/cur. @@ -1594,7 +1612,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jstring managed_nice_name, bool is_system_server, bool is_child_zygote, jstring managed_instruction_set, jstring managed_app_data_dir, bool is_top_app, - jobjectArray pkg_data_info_list, bool mount_storage_dirs) { + jobjectArray pkg_data_info_list, + jobjectArray whitelisted_data_info_list, + bool mount_data_dirs, bool mount_storage_dirs) { const char* process_name = is_system_server ? "system_server" : "zygote"; auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); @@ -1628,12 +1648,14 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation. // Isolated process / webview / app zygote should be gated by SELinux and file permission // so they can't even traverse CE / DE directories. - if (pkg_data_info_list != nullptr - && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) { - isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); + if (mount_data_dirs) { + isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list, + uid, process_name, managed_nice_name, fail_fn); isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); } - if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) { + if (mount_external != MOUNT_EXTERNAL_INSTALLER && + mount_external != MOUNT_EXTERNAL_PASS_THROUGH && + mount_storage_dirs) { BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); } @@ -2003,7 +2025,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jint mount_external, jstring se_info, jstring nice_name, jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, - jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) { + jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, + jboolean mount_data_dirs, jboolean mount_storage_dirs) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); if (UNLIKELY(managed_fds_to_close == nullptr)) { @@ -2041,6 +2064,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list, + whitelisted_data_info_list, + mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE); } return pid; @@ -2076,7 +2101,8 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( permitted_capabilities, effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true, false, nullptr, nullptr, /* is_top_app= */ false, - /* pkg_data_info_list */ nullptr, false); + /* pkg_data_info_list */ nullptr, + /* whitelisted_data_info_list */ nullptr, false, false); } else if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -2206,15 +2232,16 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess( jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, - jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) { + jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, + jboolean mount_data_dirs, jboolean mount_storage_dirs) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, - is_top_app == JNI_TRUE, pkg_data_info_list, - mount_storage_dirs == JNI_TRUE); + is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list, + mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE); } /** @@ -2405,10 +2432,19 @@ static jint com_android_internal_os_Zygote_nativeParseSigChld(JNIEnv* env, jclas return -1; } +static jboolean com_android_internal_os_Zygote_nativeSupportsTaggedPointers(JNIEnv* env, jclass) { +#ifdef __aarch64__ + int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); + return res >= 0 && res & PR_TAGGED_ADDR_ENABLE; +#else + return false; +#endif +} + static const JNINativeMethod gMethods[] = { {"nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/" - "String;Z[Ljava/lang/String;Z)I", + "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize}, {"nativeForkSystemServer", "(II[II[[IJJ)I", (void*)com_android_internal_os_Zygote_nativeForkSystemServer}, @@ -2421,7 +2457,7 @@ static const JNINativeMethod gMethods[] = { {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap}, {"nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/" - "String;Z[Ljava/lang/String;Z)V", + "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess}, {"nativeInitNativeState", "(Z)V", (void*)com_android_internal_os_Zygote_nativeInitNativeState}, @@ -2440,6 +2476,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativeBoostUsapPriority}, {"nativeParseSigChld", "([BI[I)I", (void*)com_android_internal_os_Zygote_nativeParseSigChld}, + {"nativeSupportsTaggedPointers", "()Z", + (void*)com_android_internal_os_Zygote_nativeSupportsTaggedPointers}, }; int register_com_android_internal_os_Zygote(JNIEnv* env) { diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index ab57e3dcdc53..3e007e4704a4 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2669,4 +2669,19 @@ enum PageId { // Open: Settings > Sound > Do Not Disturb > Apps > <Choose App> // OS: R DND_APPS_BYPASSING = 1840; + + // OPEN: Settings > Battery > Advanced battery option + // CATEGORY: SETTINGS + // OS: R + FUELGAUGE_ADVANCED_BATTERY_OPTION = 1842; + + // OPEN: Settings > System > Gestures > Power menu + // CATEGORY: SETTINGS + // OS: R + POWER_MENU_SETTINGS = 1843; + + // OPEN: Settings > System > Gestures > Power menu > Device controls + // CATEGORY: SETTINGS + // OS: R + DEVICE_CONTROLS_SETTINGS = 1844; } diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 684a29249294..896ee4fa42a5 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -186,4 +186,15 @@ enum EventId { RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK= 159; RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED= 160; RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET = 161; + CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_APP = 162; + CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_SETTINGS = 163; + CROSS_PROFILE_SETTINGS_PAGE_ADMIN_RESTRICTED = 164; + CROSS_PROFILE_SETTINGS_PAGE_MISSING_WORK_APP = 165; + CROSS_PROFILE_SETTINGS_PAGE_MISSING_PERSONAL_APP = 166; + CROSS_PROFILE_SETTINGS_PAGE_MISSING_INSTALL_BANNER_INTENT = 167; + CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_CLICKED = 168; + CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_NO_INTENT_CLICKED = 169; + CROSS_PROFILE_SETTINGS_PAGE_USER_CONSENTED = 170; + CROSS_PROFILE_SETTINGS_PAGE_USER_DECLINED_CONSENT = 171; + CROSS_PROFILE_SETTINGS_PAGE_PERMISSION_REVOKED = 172; } diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto index 76f8f0febf59..61b9b25fe7cf 100644 --- a/core/proto/android/stats/dnsresolver/dns_resolver.proto +++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto @@ -62,6 +62,13 @@ enum NsRcode { NS_R_NOTAUTH = 9; // Not authoritative for zone NS_R_NOTZONE = 10; // Zone of record different from zone section NS_R_MAX = 11; + // Define rcode=12~15(UNASSIGNED) in rcode enum type. + // Some DNS Servers might return undefined code to devices. + // Without the enum definition, that would be noise for our dashboard. + NS_R_UNASSIGNED12 = 12; // Unassigned + NS_R_UNASSIGNED13 = 13; // Unassigned + NS_R_UNASSIGNED14 = 14; // Unassigned + NS_R_UNASSIGNED15 = 15; // Unassigned // The following are EDNS extended rcodes NS_R_BADVERS = 16; // The following are TSIG errors @@ -170,12 +177,22 @@ enum NetworkType { NT_BLUETOOTH = 3; // Indicates this network uses an Ethernet transport. NT_ETHERNET = 4; - // Indicates this network uses a VPN transport. - NT_VPN = 5; + // Indicates this network uses a VPN transport, now deprecated. + NT_VPN = 5 [deprecated=true]; // Indicates this network uses a Wi-Fi Aware transport. NT_WIFI_AWARE = 6; // Indicates this network uses a LoWPAN transport. NT_LOWPAN = 7; + // Indicates this network uses a Cellular+VPN transport. + NT_CELLULAR_VPN = 8; + // Indicates this network uses a Wi-Fi+VPN transport. + NT_WIFI_VPN = 9; + // Indicates this network uses a Bluetooth+VPN transport. + NT_BLUETOOTH_VPN = 10; + // Indicates this network uses an Ethernet+VPN transport. + NT_ETHERNET_VPN = 11; + // Indicates this network uses a Wi-Fi+Cellular+VPN transport. + NT_WIFI_CELLULAR_VPN = 12; } enum CacheStatus{ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 451363f6bd3d..1c978bff0fd3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -351,8 +351,9 @@ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" /> <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" /> <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" /> - <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" /> - <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" /> + <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" /> + <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" /> + <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" /> @@ -4122,7 +4123,10 @@ set of pages referenced over time. <p>Declaring the permission implies intention to use the API and the user of the device can grant permission through the Settings application. - <p>Protection level: signature|privileged|appop --> + <p>Protection level: signature|privileged|appop + <p>A data loader has to be the one which provides data to install an app. + <p>A data loader has to have both permission:LOADER_USAGE_STATS AND + appop:LOADER_USAGE_STATS allowed to be able to access the read logs. --> <permission android:name="android.permission.LOADER_USAGE_STATS" android:protectionLevel="signature|privileged|appop" /> <uses-permission android:name="android.permission.LOADER_USAGE_STATS" /> @@ -5454,6 +5458,13 @@ </intent-filter> </service> + <provider + android:name="com.android.server.textclassifier.IconsContentProvider" + android:authorities="com.android.textclassifier.icons" + android:enabled="true" + android:exported="true"> + </provider> + </application> </manifest> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index b9ca29276cf0..e986b1886abf 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -245,6 +245,7 @@ android:id="@+id/notification_messaging" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="@dimen/notification_text_size" android:spacing="@dimen/notification_messaging_spacing" android:clipToPadding="false" android:clipChildren="false" @@ -263,53 +264,65 @@ <include layout="@layout/notification_material_action_list" /> </com.android.internal.widget.RemeasuringLinearLayout> - <!--This is dynamically placed between here and at the end of the layout--> - <LinearLayout + <!--This is dynamically placed between here and at the end of the layout. It starts here since + only FrameLayout layout params have gravity--> + <FrameLayout android:id="@+id/expand_button_container" android:layout_width="wrap_content" - android:layout_height="@dimen/conversation_expand_button_size" + android:layout_height="match_parent" android:layout_gravity="end|top" - android:paddingStart="16dp" - android:orientation="horizontal" - android:paddingEnd="@dimen/notification_content_margin_end" - android:clipToPadding="false" android:clipChildren="false" - > - <!-- Images --> - <com.android.internal.widget.MessagingLinearLayout - android:id="@+id/conversation_image_message_container" - android:forceHasOverlappingRendering="false" - android:layout_width="40dp" - android:layout_height="40dp" - android:layout_marginEnd="11dp" - android:spacing="0dp" - android:layout_gravity="center" + android:clipToPadding="false"> + <!--This layout makes sure that we can nicely center the expand content in the + collapsed layout while the parent makes sure that we're never laid out bigger + than the messaging content.--> + <LinearLayout + android:id="@+id/expand_button_inner_container" + android:layout_width="wrap_content" + android:layout_height="@dimen/conversation_expand_button_size" + android:paddingStart="16dp" + android:orientation="horizontal" + android:layout_gravity="end|top" + android:paddingEnd="@dimen/notification_content_margin_end" android:clipToPadding="false" android:clipChildren="false" - /> - <!-- Unread Count --> - <TextView - android:id="@+id/conversation_unread_count" - android:layout_width="33sp" - android:layout_height="wrap_content" - android:layout_marginEnd="11dp" - android:layout_gravity="center" - android:gravity="center" - android:padding="2dp" - android:visibility="gone" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification" - android:textColor="#FFFFFF" - android:textSize="12sp" - android:background="@drawable/conversation_unread_bg" - /> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" - android:layout_gravity="center" - android:drawable="@drawable/ic_expand_notification" - android:clickable="false" - android:importantForAccessibility="no" - /> - </LinearLayout> + > + <!-- Images --> + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/conversation_image_message_container" + android:forceHasOverlappingRendering="false" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_marginEnd="11dp" + android:spacing="0dp" + android:layout_gravity="center" + android:clipToPadding="false" + android:clipChildren="false" + /> + <!-- Unread Count --> + <TextView + android:id="@+id/conversation_unread_count" + android:layout_width="33sp" + android:layout_height="wrap_content" + android:layout_marginEnd="11dp" + android:layout_gravity="center" + android:gravity="center" + android:padding="2dp" + android:visibility="gone" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification" + android:textColor="#FFFFFF" + android:textSize="12sp" + android:background="@drawable/conversation_unread_bg" + /> + <com.android.internal.widget.NotificationExpandButton + android:id="@+id/expand_button" + android:layout_width="@dimen/notification_header_expand_icon_size" + android:layout_height="@dimen/notification_header_expand_icon_size" + android:layout_gravity="center" + android:drawable="@drawable/ic_expand_notification" + android:clickable="false" + android:importantForAccessibility="no" + /> + </LinearLayout> + </FrameLayout> </com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index c7e1a21aa642..5890bed11b07 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -19,61 +19,66 @@ android:id="@+id/resolver_empty_state" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:gravity="center_horizontal" android:visibility="gone" - android:paddingTop="48dp" android:paddingStart="24dp" android:paddingEnd="24dp"> - <ImageView - android:id="@+id/resolver_empty_state_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_centerHorizontal="true" /> - <TextView - android:id="@+id/resolver_empty_state_title" - android:layout_below="@+id/resolver_empty_state_icon" - android:layout_marginTop="8dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:fontFamily="@string/config_headlineFontFamilyMedium" - android:textColor="@color/resolver_empty_state_text" - android:textSize="14sp" - android:layout_centerHorizontal="true" /> - <TextView - android:id="@+id/resolver_empty_state_subtitle" - android:layout_below="@+id/resolver_empty_state_title" - android:layout_marginTop="4dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/resolver_empty_state_text" - android:textSize="12sp" - android:gravity="center_horizontal" - android:layout_centerHorizontal="true" /> - <Button - android:id="@+id/resolver_empty_state_button" - android:layout_below="@+id/resolver_empty_state_subtitle" - android:layout_marginTop="16dp" - android:text="@string/resolver_switch_on_work" - android:layout_width="wrap_content" + <RelativeLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:fontFamily="@string/config_headlineFontFamilyMedium" - android:textSize="14sp" - android:textColor="?attr/colorAccent" - android:layout_centerHorizontal="true" - android:background="@drawable/resolver_turn_on_work_button_ripple_background"/> - <ProgressBar - android:id="@+id/resolver_empty_state_progress" - style="@style/Widget.Material.Light.ProgressBar" - android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:indeterminate="true" - android:layout_alignTop="@+id/resolver_empty_state_icon" - android:layout_alignBottom="@+id/resolver_empty_state_button" - android:layout_centerHorizontal="true" - android:layout_below="@+id/resolver_empty_state_subtitle" - android:indeterminateTint="?attr/colorAccent"/> + android:paddingTop="48dp" + android:paddingBottom="48dp" + android:gravity="center_horizontal"> + <ImageView + android:id="@+id/resolver_empty_state_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_centerHorizontal="true" /> + <TextView + android:id="@+id/resolver_empty_state_title" + android:layout_below="@+id/resolver_empty_state_icon" + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@string/config_headlineFontFamilyMedium" + android:textColor="@color/resolver_empty_state_text" + android:textSize="14sp" + android:layout_centerHorizontal="true" /> + <TextView + android:id="@+id/resolver_empty_state_subtitle" + android:layout_below="@+id/resolver_empty_state_title" + android:layout_marginTop="4dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/resolver_empty_state_text" + android:textSize="12sp" + android:gravity="center_horizontal" + android:layout_centerHorizontal="true" /> + <Button + android:id="@+id/resolver_empty_state_button" + android:layout_below="@+id/resolver_empty_state_subtitle" + android:layout_marginTop="16dp" + android:text="@string/resolver_switch_on_work" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@string/config_headlineFontFamilyMedium" + android:textSize="14sp" + android:textColor="?attr/colorAccent" + android:layout_centerHorizontal="true" + android:background="@drawable/resolver_turn_on_work_button_ripple_background"/> + <ProgressBar + android:id="@+id/resolver_empty_state_progress" + style="@style/Widget.Material.Light.ProgressBar" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:indeterminate="true" + android:layout_alignTop="@+id/resolver_empty_state_icon" + android:layout_alignBottom="@+id/resolver_empty_state_button" + android:layout_centerHorizontal="true" + android:layout_below="@+id/resolver_empty_state_subtitle" + android:indeterminateTint="?attr/colorAccent"/> + </RelativeLayout> <TextView android:id="@+id/empty" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 11dda41d0b57..ef1e8b74b05f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3866,6 +3866,7 @@ <java-symbol type="id" name="conversation_icon_badge_ring" /> <java-symbol type="id" name="conversation_icon_badge_bg" /> <java-symbol type="id" name="expand_button_container" /> + <java-symbol type="id" name="expand_button_inner_container" /> <java-symbol type="id" name="messaging_group_content_container" /> <java-symbol type="id" name="expand_button_and_content_container" /> <java-symbol type="id" name="conversation_header" /> diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java new file mode 100644 index 000000000000..630e16ac80d4 --- /dev/null +++ b/core/tests/coretests/src/android/app/WindowContextTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link WindowContext} + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:WindowContextTest + * + * <p>This test class is a part of Window Manager Service tests and specified in + * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. + */ +@FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.") +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowContextTest { + @Test + public void testWindowContextRelease_doRemoveWindowToken() throws Throwable { + final Context instContext = InstrumentationRegistry.getInstrumentation() + .getTargetContext(); + final Display display = instContext.getSystemService(DisplayManager.class) + .getDisplay(DEFAULT_DISPLAY); + final Context context = instContext.createDisplayContext(display); + final WindowContext windowContext = new WindowContext(context, TYPE_APPLICATION_OVERLAY, + null /* options */); + + final IBinder token = windowContext.getActivityToken(); + + final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); + assertTrue("Token must be registered to WMS", wms.isWindowToken(token)); + + windowContext.release(); + + assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token)); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index b9c71a2a207c..d432dda4a1be 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -638,7 +639,31 @@ public class InsetsControllerTest { }); } + @Test + public void testFrameUpdateDuringAnimation() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + + mController.onControlsChanged(createSingletonControl(ITYPE_IME)); + // Pretend IME is calling + mController.show(ime(), true /* fromIme */); + + InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); + copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3); + copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7)); + mController.onStateChanged(copy); + assertNotEquals(new Rect(0, 1, 2, 3), + mController.getState().getSource(ITYPE_IME).getFrame()); + assertNotEquals(new Rect(4, 5, 6, 7), + mController.getState().getSource(ITYPE_IME).getVisibleFrame()); + mController.cancelExistingAnimation(); + assertEquals(new Rect(0, 1, 2, 3), + mController.getState().getSource(ITYPE_IME).getFrame()); + assertEquals(new Rect(4, 5, 6, 7), + mController.getState().getSource(ITYPE_IME).getVisibleFrame()); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } @Test public void testCaptionInsetsStateAssemble() { @@ -671,8 +696,7 @@ public class InsetsControllerTest { // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will // attempt to release mLeash directly. - SurfaceControl copy = new SurfaceControl(); - copy.copyFrom(mLeash); + SurfaceControl copy = new SurfaceControl(mLeash); return new InsetsSourceControl(type, copy, new Point()); } diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java index 74524bf6d76f..ddc977d380ae 100644 --- a/core/tests/coretests/src/android/view/WindowMetricsTest.java +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -56,17 +56,17 @@ public class WindowMetricsTest { @Before public void setUp() { - final Context insetContext = InstrumentationRegistry.getInstrumentation() + final Context instContext = InstrumentationRegistry.getInstrumentation() .getTargetContext(); - final Display display = insetContext.getSystemService(DisplayManager.class) + final Display display = instContext.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); - mWindowContext = insetContext.createDisplayContext(display) + mWindowContext = instContext.createDisplayContext(display) .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */); mWm = mWindowContext.getSystemService(WindowManager.class); } @Test - public void testAddViewANdRemoveView_GetMetrics_DoNotCrash() { + public void testAddViewAndRemoveView_GetMetrics_DoNotCrash() { final View view = new View(mWindowContext); final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index bc0cdc1e029b..583c75102d52 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1339,15 +1339,8 @@ public class ChooserActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( - Mockito.isA(List.class), - Mockito.isA(UserHandle.class))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - // When work profile is disabled, we get 0 results when we query the work profile - // intents. - setupResolverControllers(personalResolvedComponentInfos, - /* workResolvedComponentInfos */ new ArrayList<>()); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 0bf8663c7a85..eb39d58019d9 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -614,15 +614,8 @@ public class ResolverActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( - Mockito.isA(List.class), - Mockito.isA(UserHandle.class))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - // When work profile is disabled, we get 0 results when we query the work profile - // intents. - setupResolverControllers(personalResolvedComponentInfos, - /* workResolvedComponentInfos */ new ArrayList<>()); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp index a2fcef56b780..2b38cca2db36 100644 --- a/core/tests/overlaytests/host/Android.bp +++ b/core/tests/overlaytests/host/Android.bp @@ -16,7 +16,7 @@ java_test_host { name: "OverlayHostTests", srcs: ["src/**/*.java"], libs: ["tradefed"], - test_suites: ["general-tests"], + test_suites: ["device-tests"], target_required: [ "OverlayHostTests_NonPlatformSignatureOverlay", "OverlayHostTests_PlatformSignatureStaticOverlay", diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index eec7be22c78b..d898d222b8de 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -78,14 +78,9 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } @Test - public void failToInstallPlatformSignedStaticOverlay() throws Exception { - try { - installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk"); - fail("installed a static overlay"); - } catch (Exception e) { - // Expected. - } - assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); + public void installedIsStaticOverlayIsMutable() throws Exception { + installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk"); + assertTrue(isOverlayMutable(SIG_OVERLAY_PACKAGE_NAME)); } @Test @@ -229,6 +224,10 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { return shell("cmd overlay list").contains(pkg); } + private boolean isOverlayMutable(String pkg) throws Exception { + return shell("cmd overlay dump ismutable " + pkg).contains("true"); + } + private String shell(final String cmd) throws Exception { return getDevice().executeShellCommand(cmd); } diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk index cc7704b0ce98..f3c0abd8293f 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk @@ -20,7 +20,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad include $(BUILD_PACKAGE) @@ -28,7 +28,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform LOCAL_MANIFEST_FILE := static/AndroidManifest.xml LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static include $(BUILD_PACKAGE) @@ -37,7 +38,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk index f8607f44bda6..878f05d57662 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -19,7 +19,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal @@ -31,7 +31,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 @@ -43,7 +43,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 @@ -57,7 +57,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res @@ -68,7 +68,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/Android.bp index 3825c55f3b4b..9baedf81d6e0 100644 --- a/core/tests/overlaytests/remount/host/Android.bp +++ b/core/tests/overlaytests/remount/Android.bp @@ -24,5 +24,7 @@ java_test_host { ":OverlayRemountedTest_SharedLibrary", ":OverlayRemountedTest_SharedLibraryOverlay", ":OverlayRemountedTest_Target", + ":OverlayRemountedTest_TargetUpgrade", + ":OverlayRemountedTest_Overlay", ], } diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/AndroidTest.xml index 087b7313693d..087b7313693d 100644 --- a/core/tests/overlaytests/remount/host/AndroidTest.xml +++ b/core/tests/overlaytests/remount/AndroidTest.xml diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java new file mode 100644 index 000000000000..00a53c45d797 --- /dev/null +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import static org.junit.Assert.fail; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; + +public class OverlayRemountedTestBase extends BaseHostJUnit4Test { + static final long ASSERT_RESOURCE_TIMEOUT_MS = 30000; + + static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; + static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; + static final String TARGET_UPGRADE_APK = "OverlayRemountedTest_TargetUpgrade.apk"; + static final String OVERLAY_APK = "OverlayRemountedTest_Overlay.apk"; + static final String OVERLAY_PACKAGE = "com.android.overlaytest.remounted.target.overlay"; + static final String SHARED_LIBRARY_APK = + "OverlayRemountedTest_SharedLibrary.apk"; + static final String SHARED_LIBRARY_PACKAGE = + "com.android.overlaytest.remounted.shared_library"; + static final String SHARED_LIBRARY_OVERLAY_APK = + "OverlayRemountedTest_SharedLibraryOverlay.apk"; + static final String SHARED_LIBRARY_OVERLAY_PACKAGE = + "com.android.overlaytest.remounted.shared_library.overlay"; + + private final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + protected final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, + this::getDevice); + + @Rule + public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer); + + @Before + public void startBefore() throws DeviceNotAvailableException { + getDevice().waitForDeviceAvailable(); + } + + /** Builds the full name of a resource in the form package:type/entry. */ + String resourceName(String pkg, String type, String entry) { + return String.format("%s:%s/%s", pkg, type, entry); + } + + void assertResource(String resourceName, String expectedValue) + throws DeviceNotAvailableException { + String result = null; + + final long endMillis = System.currentTimeMillis() + ASSERT_RESOURCE_TIMEOUT_MS; + while (System.currentTimeMillis() <= endMillis) { + result = getDevice().executeShellCommand( + String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName)); + if (result.equals(expectedValue + "\n") || + result.endsWith("-> " + expectedValue + "\n")) { + return; + } + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } + } + + fail(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result)); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java index 06b2ac8f9e22..49d1894c9bae 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java @@ -16,42 +16,13 @@ package com.android.overlaytest.remounted; -import static org.junit.Assert.assertTrue; - -import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.RuleChain; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) -public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { - private static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; - private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; - private static final String SHARED_LIBRARY_APK = - "OverlayRemountedTest_SharedLibrary.apk"; - private static final String SHARED_LIBRARY_PACKAGE = - "com.android.overlaytest.remounted.shared_library"; - private static final String SHARED_LIBRARY_OVERLAY_APK = - "OverlayRemountedTest_SharedLibraryOverlay.apk"; - private static final String SHARED_LIBRARY_OVERLAY_PACKAGE = - "com.android.overlaytest.remounted.shared_library.overlay"; - - public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice); - - @Rule - public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer); - - @Before - public void startBefore() throws DeviceNotAvailableException { - getDevice().waitForDeviceAvailable(); - } +public class OverlaySharedLibraryTest extends OverlayRemountedTestBase { @Test public void testSharedLibrary() throws Exception { @@ -60,7 +31,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .reboot() .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false) @@ -71,7 +42,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { assertResource(libraryResource, "false"); // Overlay the shared library resource. - preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); + mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); assertResource(targetResource, "true"); assertResource(libraryResource, "true"); } @@ -83,7 +54,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true) .reboot() @@ -92,18 +63,4 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { assertResource(targetResource, "true"); assertResource(libraryResource, "true"); } - - /** Builds the full name of a resource in the form package:type/entry. */ - String resourceName(String pkg, String type, String entry) { - return String.format("%s:%s/%s", pkg, type, entry); - } - - void assertResource(String resourceName, String expectedValue) - throws DeviceNotAvailableException { - final String result = getDevice().executeShellCommand( - String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName)); - assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result), - result.equals(expectedValue + "\n") || - result.endsWith("-> " + expectedValue + "\n")); - } } diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java new file mode 100644 index 000000000000..f685ec1580ad --- /dev/null +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class PackagedUpgradedTest extends OverlayRemountedTestBase { + + @Test + public void testTargetUpgrade() throws Exception { + final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + final String targetReference = resourceName(TARGET_PACKAGE, "bool", "target_reference"); + + mPreparer.pushResourceFile(TARGET_APK, "/product/app/OverlayTarget.apk") + .reboot() + .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE) + .setOverlayEnabled(OVERLAY_PACKAGE, true); + + assertResource(targetReference, "@" + 0x7f010000 + " -> true"); + assertResource(targetOverlaid, "true"); + + mPreparer.installResourceApk(TARGET_UPGRADE_APK, TARGET_PACKAGE); + + assertResource(targetReference, "@" + 0x7f0100ff + " -> true"); + assertResource(targetOverlaid, "true"); + } + + @Test + public void testTargetRelocated() throws Exception { + final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + final String originalPath = "/product/app/OverlayTarget.apk"; + + mPreparer.pushResourceFile(TARGET_APK, originalPath) + .reboot() + .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE) + .setOverlayEnabled(OVERLAY_PACKAGE, true); + + assertResource(targetOverlaid, "true"); + + mPreparer.remount(); + getDevice().deleteFile(originalPath); + mPreparer.pushResourceFile(TARGET_UPGRADE_APK, "/product/app/OverlayTarget2.apk") + .reboot(); + + assertResource(targetOverlaid, "true"); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java index 8696091239c2..b45b8eabf7c0 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java @@ -18,8 +18,6 @@ package com.android.overlaytest.remounted; import static org.junit.Assert.assertTrue; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; @@ -32,10 +30,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeoutException; class SystemPreparer extends ExternalResource { private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; @@ -58,7 +52,7 @@ class SystemPreparer extends ExternalResource { SystemPreparer pushResourceFile(String resourcePath, String outputPath) throws DeviceNotAvailableException, IOException { final ITestDevice device = mDeviceProvider.getDevice(); - device.executeAdbCommand("remount"); + remount(); assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); mPushedFiles.add(outputPath); return this; @@ -69,7 +63,7 @@ class SystemPreparer extends ExternalResource { throws DeviceNotAvailableException, IOException { final ITestDevice device = mDeviceProvider.getDevice(); final File tmpFile = copyResourceToTemp(resourcePath); - final String result = device.installPackage(tmpFile, true); + final String result = device.installPackage(tmpFile, true /* reinstall */); Assert.assertNull(result); mInstalledPackages.add(packageName); return this; @@ -77,34 +71,29 @@ class SystemPreparer extends ExternalResource { /** Sets the enable state of an overlay pacakage. */ SystemPreparer setOverlayEnabled(String packageName, boolean enabled) - throws ExecutionException, DeviceNotAvailableException { + throws DeviceNotAvailableException { final ITestDevice device = mDeviceProvider.getDevice(); + final String enable = enabled ? "enable" : "disable"; // Wait for the overlay to change its enabled state. - final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> { - while (true) { - device.executeShellCommand(String.format("cmd overlay %s %s", - enabled ? "enable" : "disable", packageName)); - - final String result = device.executeShellCommand("cmd overlay dump " + packageName); - final int startIndex = result.indexOf("mIsEnabled"); - final int endIndex = result.indexOf('\n', startIndex); - if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) { - return true; - } + final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS; + String result; + while (System.currentTimeMillis() <= endMillis) { + device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName)); + result = device.executeShellCommand("cmd overlay dump isenabled " + + packageName); + if (((enabled) ? "true\n" : "false\n").equals(result)) { + return this; } - }); - final Executor executor = (cmd) -> new Thread(cmd).start(); - executor.execute(enabledListener); - try { - enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS); - } catch (InterruptedException ignored) { - } catch (TimeoutException e) { - throw new IllegalStateException(device.executeShellCommand("cmd overlay list")); + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } } - return this; + throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable, + packageName, device.executeShellCommand("cmd overlay list"))); } /** Restarts the device and waits until after boot is completed. */ @@ -114,6 +103,11 @@ class SystemPreparer extends ExternalResource { return this; } + SystemPreparer remount() throws DeviceNotAvailableException { + mDeviceProvider.getDevice().executeAdbCommand("remount"); + return this; + } + /** Copies a file within the host test jar to a temporary file on the host machine. */ private File copyResourceToTemp(String resourcePath) throws IOException { final File tempFile = mHostTempFolder.newFile(resourcePath); @@ -138,7 +132,7 @@ class SystemPreparer extends ExternalResource { protected void after() { final ITestDevice device = mDeviceProvider.getDevice(); try { - device.executeAdbCommand("remount"); + remount(); for (final String file : mPushedFiles) { device.deleteFile(file); } diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp new file mode 100644 index 000000000000..447601972f78 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp @@ -0,0 +1,18 @@ +// 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. + +android_test_helper_app { + name: "OverlayRemountedTest_Overlay", + sdk_version: "current", +} diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml new file mode 100644 index 000000000000..d6d706c2da62 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.target.overlay"> + <application android:hasCode="false" /> + <overlay android:targetPackage="com.android.overlaytest.remounted.target" + android:targetName="TestResources" /> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml new file mode 100644 index 000000000000..675e44f19f95 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <bool name="target_overlaid">true</bool> +</resources> diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp index ffb0572c3fd0..ffb0572c3fd0 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml index 06e3f6a99410..06e3f6a99410 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml index 1b06f6d7530b..1b06f6d7530b 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml index 5b9db163a274..5b9db163a274 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml index 2dc47a7ecf61..2dc47a7ecf61 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp index 0d29aec909d5..0d29aec909d5 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml index 53a4e61949da..53a4e61949da 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml index f66448a8d991..f66448a8d991 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp index 83f9f28b3f48..a8910eb8dae2 100644 --- a/core/tests/overlaytests/remount/target/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/Target/Android.bp @@ -1,4 +1,4 @@ -// Copyright (C) 2018 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,7 +14,12 @@ android_test_helper_app { name: "OverlayRemountedTest_Target", - srcs: ["src/**/*.java"], sdk_version: "test_current", libs: ["OverlayRemountedTest_SharedLibrary"], } + +android_test_helper_app { + name: "OverlayRemountedTest_TargetUpgrade", + resource_dirs: ["res_upgrade"], + sdk_version: "test_current", +} diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml index dc07dca16718..d1c7b7e8bb9d 100644 --- a/core/tests/overlaytests/remount/target/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml @@ -19,8 +19,7 @@ package="com.android.overlaytest.remounted.target"> <application> - <uses-library android:name="android.test.runner" /> <uses-library android:name="com.android.overlaytest.remounted.shared_library" - android:required="true" /> + android:required="false" /> </application> </manifest> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml new file mode 100644 index 000000000000..4aa5bcee8f3d --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="target_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml index b5f444a5eb72..76253a95b766 100644 --- a/core/tests/overlaytests/remount/target/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml @@ -17,4 +17,10 @@ <resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library"> <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool> + + <!-- This resource has a different id in the updated version of this target app to test that the + idmap is regenerated when the target is updated. --> + <bool name="target_overlaid">false</bool> + <public type="bool" name="target_overlaid" id="0x7f010000" /> + <bool name="target_reference">@bool/target_overlaid</bool> </resources> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml new file mode 100644 index 000000000000..4aa5bcee8f3d --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="target_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml new file mode 100644 index 000000000000..f552cb0776ab --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <!-- This resource has a different id in the updated target app than the base target app to test + that the idmap is regenerated when the target is updated. --> + <bool name="target_overlaid">false</bool> + <public type="bool" name="target_overlaid" id="0x7f0100ff" /> + <bool name="target_reference">@bool/target_overlaid</bool> +</resources>
\ No newline at end of file diff --git a/core/xsd/vts/Android.bp b/core/xsd/vts/Android.bp index a2a2168c8377..ca655f18149c 100644 --- a/core/xsd/vts/Android.bp +++ b/core/xsd/vts/Android.bp @@ -36,7 +36,7 @@ cc_test { ], test_suites: [ "general-tests", - "vts-core" + "vts" ], test_config: "vts_permission_validate_test.xml", } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 59bdf3dad43e..0389639e9edb 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -137,6 +137,7 @@ applications that come with the platform <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <permission name="android.permission.PACKAGE_USAGE_STATS" /> + <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> </privapp-permissions> <privapp-permissions package="com.android.phone"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b75370486afe..18086ec0313e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -25,6 +25,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-2101985723": { + "message": "Failed looking up window session=%s callers=%s", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "-2072089308": { "message": "Attempted to add window with token that is a sub-window: %s. Aborting.", "level": "WARN", @@ -655,12 +661,6 @@ "group": "WM_DEBUG_SCREEN_ON", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-747671114": { - "message": "Failed looking up window callers=%s", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-714291355": { "message": "Losing delayed focus: %s", "level": "INFO", @@ -883,6 +883,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-242787066": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -901,6 +907,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "-172900257": { + "message": "addTaskToTargets, target: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/RecentsAnimationController.java" + }, "-167822951": { "message": "Attempted to add starting window to token with already existing starting window", "level": "WARN", @@ -1201,6 +1213,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowSurfaceController.java" }, + "315395835": { + "message": "Trying to add window with invalid user=%d", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "342460966": { "message": "DRAG %s: pos=(%d,%d)", "level": "INFO", @@ -1507,12 +1525,30 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "838570988": { + "message": "Could not report token removal to the window token client.", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowToken.java" + }, + "845234215": { + "message": "App is requesting an orientation, return %d for display id=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "853091290": { "message": "Moved stack=%s behind stack=%s", "level": "DEBUG", "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "854237232": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/Task.java" + }, "873914452": { "message": "goodToGo()", "level": "DEBUG", diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 918e7af12d31..05f4d6b63a4c 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -385,7 +385,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap const StringPiece idmap_data( reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)), static_cast<size_t>(idmap_asset->getLength())); - std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data); + std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data); if (loaded_idmap == nullptr) { LOG(ERROR) << "failed to load IDMAP " << idmap_path; return {}; @@ -538,8 +538,9 @@ bool ApkAssets::IsUpToDate() const { // Loaders are invalidated by the app, not the system, so assume they are up to date. return true; } + return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) && + last_mod_time_ == getFileModDate(path_.c_str()); - return last_mod_time_ == getFileModDate(path_.c_str()); } } // namespace android diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 0b2fd9ec982d..eb6ee9525bb9 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -20,6 +20,7 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/misc.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" #include "utils/ByteOrder.h" @@ -192,7 +193,9 @@ static bool IsValidIdmapHeader(const StringPiece& data) { return true; } -LoadedIdmap::LoadedIdmap(const Idmap_header* header, +LoadedIdmap::LoadedIdmap(std::string&& idmap_path, + const time_t last_mod_time, + const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_overlay_entry* overlay_entries, @@ -201,7 +204,9 @@ LoadedIdmap::LoadedIdmap(const Idmap_header* header, data_header_(data_header), target_entries_(target_entries), overlay_entries_(overlay_entries), - string_pool_(string_pool) { + string_pool_(string_pool), + idmap_path_(std::move(idmap_path)), + idmap_last_mod_time_(last_mod_time) { size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path), arraysize(header_->overlay_path)); @@ -212,7 +217,8 @@ LoadedIdmap::LoadedIdmap(const Idmap_header* header, target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length); } -std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) { +std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, + const StringPiece& idmap_data) { ATRACE_CALL(); if (!IsValidIdmapHeader(idmap_data)) { return {}; @@ -275,10 +281,14 @@ std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_da // Can't use make_unique because LoadedIdmap constructor is private. std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(header, data_header, target_entries, overlay_entries, - idmap_string_pool.release())); + new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header, + data_header, target_entries, overlay_entries, idmap_string_pool.release())); return std::move(loaded_idmap); } +bool LoadedIdmap::IsUpToDate() const { + return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str()); +} + } // namespace android diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index ccb57f373473..ecc1ce65d124 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -142,7 +142,13 @@ class IdmapResMap { class LoadedIdmap { public: // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed. - static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data); + static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path, + const StringPiece& idmap_data); + + // Returns the path to the IDMAP. + inline const std::string& IdmapPath() const { + return idmap_path_; + } // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated. inline const std::string& OverlayApkPath() const { @@ -167,6 +173,10 @@ class LoadedIdmap { return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id); } + // Returns whether the idmap file on disk has not been modified since the construction of this + // LoadedIdmap. + bool IsUpToDate() const; + protected: // Exposed as protected so that tests can subclass and mock this class out. LoadedIdmap() = default; @@ -177,13 +187,17 @@ class LoadedIdmap { const Idmap_overlay_entry* overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; + const std::string idmap_path_; std::string overlay_apk_path_; std::string target_apk_path_; + const time_t idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); - explicit LoadedIdmap(const Idmap_header* header, + explicit LoadedIdmap(std::string&& idmap_path, + time_t last_mod_time, + const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_overlay_entry* overlay_entries, diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index 41ba637da5d7..7aa0dbbafab3 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -38,7 +38,7 @@ class IdmapTest : public ::testing::Test { protected: void SetUp() override { // Move to the test data directory so the idmap can locate the overlay APK. - std::string original_path = base::GetExecutableDirectory(); + original_path = base::GetExecutableDirectory(); chdir(GetTestDataPath().c_str()); system_assets_ = ApkAssets::Load("system/system.apk"); @@ -49,10 +49,14 @@ class IdmapTest : public ::testing::Test { overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk"); ASSERT_NE(nullptr, overlayable_assets_); + } + + void TearDown() override { chdir(original_path.c_str()); } protected: + std::string original_path; std::unique_ptr<const ApkAssets> system_assets_; std::unique_ptr<const ApkAssets> overlay_assets_; std::unique_ptr<const ApkAssets> overlayable_assets_; @@ -221,8 +225,7 @@ TEST_F(IdmapTest, OverlaidResourceHasSameName) { TEST_F(IdmapTest, OverlayLoaderInterop) { std::string contents; - auto loader_assets = ApkAssets::LoadTable(GetTestDataPath() + "/loader/resources.arsc", - PROPERTY_LOADER); + auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER); AssetManager2 asset_manager; asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(), @@ -241,4 +244,25 @@ TEST_F(IdmapTest, OverlayLoaderInterop) { ASSERT_EQ(GetStringFromApkAssets(asset_manager, val, cookie), "loader"); } +TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { + std::string idmap_contents; + ASSERT_TRUE(base::ReadFileToString("overlay/overlay.idmap", &idmap_contents)); + + TemporaryFile temp_file; + ASSERT_TRUE(base::WriteStringToFile(idmap_contents, temp_file.path)); + + auto apk_assets = ApkAssets::LoadOverlay(temp_file.path); + ASSERT_NE(nullptr, apk_assets); + ASSERT_TRUE(apk_assets->IsUpToDate()); + + unlink(temp_file.path); + ASSERT_FALSE(apk_assets->IsUpToDate()); + sleep(2); + + base::WriteStringToFile("hello", temp_file.path); + sleep(2); + + ASSERT_FALSE(apk_assets->IsUpToDate()); +} + } // namespace diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index ac2fd98248d0..aa842ff6a7b7 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -171,7 +171,6 @@ cc_library_headers { cc_defaults { name: "android_graphics_apex", - host_supported: true, cflags: [ "-Wno-unused-parameter", "-Wno-non-virtual-dtor", @@ -231,7 +230,6 @@ cc_library_headers { cc_defaults { name: "android_graphics_jni", - host_supported: true, cflags: [ "-Wno-unused-parameter", "-Wno-non-virtual-dtor", @@ -537,7 +535,11 @@ cc_defaults { cc_test { name: "hwui_unit_tests", - defaults: ["hwui_test_defaults"], + defaults: [ + "hwui_test_defaults", + "android_graphics_apex", + "android_graphics_jni", + ], static_libs: [ "libgmock", @@ -549,6 +551,7 @@ cc_test { srcs: [ "tests/unit/main.cpp", + "tests/unit/ABitmapTests.cpp", "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", "tests/unit/CommonPoolTests.cpp", diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp index b56a619b00aa..3780ba072308 100644 --- a/libs/hwui/apex/android_bitmap.cpp +++ b/libs/hwui/apex/android_bitmap.cpp @@ -163,10 +163,9 @@ jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat forma void ABitmap_notifyPixelsChanged(ABitmap* bitmapHandle) { Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); - if (bitmap->isImmutable()) { - ALOGE("Attempting to modify an immutable Bitmap!"); + if (!bitmap->isImmutable()) { + bitmap->notifyPixelsChanged(); } - return bitmap->notifyPixelsChanged(); } namespace { diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index 055075d0c42a..1ff156593c41 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -183,7 +183,7 @@ public: } ~InvokeListener() override { - auto* env = get_env_or_die(mJvm); + auto* env = requireEnv(mJvm); env->DeleteWeakGlobalRef(mWeakRef); } diff --git a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp index db5f6f6c684f..b10540cb3fbd 100644 --- a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp +++ b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp @@ -9,24 +9,6 @@ using namespace android; static jmethodID gByteBuffer_getMethodID; static jmethodID gByteBuffer_setPositionMethodID; -/** - * Helper method for accessing the JNI interface pointer. - * - * Image decoding (which this supports) is started on a thread that is already - * attached to the Java VM. But an AnimatedImageDrawable continues decoding on - * the AnimatedImageThread, which is not attached. This will attach if - * necessary. - */ -static JNIEnv* requireEnv(JavaVM* jvm) { - JNIEnv* env; - if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - if (jvm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); - } - } - return env; -} - class ByteBufferStream : public SkStreamAsset { private: ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length, @@ -304,7 +286,7 @@ std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jby auto* context = new release_proc_context{jvm, jbyteBuffer}; auto releaseProc = [](const void*, void* context) { auto* c = reinterpret_cast<release_proc_context*>(context); - JNIEnv* env = get_env_or_die(c->jvm); + JNIEnv* env = requireEnv(c->jvm); env->DeleteGlobalRef(c->jbyteBuffer); delete c; }; diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp index 39483b55992b..f1c6b29204b2 100644 --- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp +++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp @@ -49,13 +49,13 @@ public: } ~JavaInputStreamAdaptor() override { - auto* env = android::get_env_or_die(fJvm); + auto* env = android::requireEnv(fJvm); env->DeleteGlobalRef(fJavaInputStream); env->DeleteGlobalRef(fJavaByteArray); } size_t read(void* buffer, size_t size) override { - auto* env = android::get_env_or_die(fJvm); + auto* env = android::requireEnv(fJvm); if (!fSwallowExceptions && checkException(env)) { // Just in case the caller did not clear from a previous exception. return 0; diff --git a/libs/hwui/jni/Utils.cpp b/libs/hwui/jni/Utils.cpp index 17c194d04f84..34fd6687d52c 100644 --- a/libs/hwui/jni/Utils.cpp +++ b/libs/hwui/jni/Utils.cpp @@ -159,3 +159,13 @@ JNIEnv* android::get_env_or_die(JavaVM* jvm) { } return env; } + +JNIEnv* android::requireEnv(JavaVM* jvm) { + JNIEnv* env; + if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (jvm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} diff --git a/libs/hwui/jni/Utils.h b/libs/hwui/jni/Utils.h index 89255177ba2e..f628cc3c85ed 100644 --- a/libs/hwui/jni/Utils.h +++ b/libs/hwui/jni/Utils.h @@ -78,6 +78,16 @@ bool isSeekable(int descriptor); JNIEnv* get_env_or_die(JavaVM* jvm); +/** + * Helper method for accessing the JNI interface pointer. + * + * Image decoding (which this supports) is started on a thread that is already + * attached to the Java VM. But an AnimatedImageDrawable continues decoding on + * the AnimatedImageThread, which is not attached. This will attach if + * necessary. + */ +JNIEnv* requireEnv(JavaVM* jvm); + }; // namespace android #endif // _ANDROID_GRAPHICS_UTILS_H_ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 41aa1ff80e3c..5088494d6a07 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -418,9 +418,9 @@ void SkiaPipeline::endCapture(SkSurface* surface) { auto data = picture->serialize(); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; + mCaptureMode = CaptureMode::None; } } - mCaptureMode = CaptureMode::None; mRecorder.reset(); } } diff --git a/libs/hwui/tests/unit/ABitmapTests.cpp b/libs/hwui/tests/unit/ABitmapTests.cpp new file mode 100644 index 000000000000..8e2f7e09d406 --- /dev/null +++ b/libs/hwui/tests/unit/ABitmapTests.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "android/graphics/bitmap.h" +#include "apex/TypeCast.h" +#include "hwui/Bitmap.h" +#include "tests/common/TestUtils.h" + +using namespace android; +using namespace android::uirenderer; + +TEST(ABitmap, notifyPixelsChanged) { + // generate a bitmap and its public API handle + sk_sp<Bitmap> bitmap(TestUtils::createBitmap(1, 1)); + ABitmap* abmp = android::TypeCast::toABitmap(bitmap.get()); + + // verify that notification changes the genID + uint32_t genID = bitmap->getGenerationID(); + ABitmap_notifyPixelsChanged(abmp); + ASSERT_TRUE(bitmap->getGenerationID() != genID); + + // mark the bitmap as immutable + ASSERT_FALSE(bitmap->isImmutable()); + bitmap->setImmutable(); + ASSERT_TRUE(bitmap->isImmutable()); + + // attempt to notify that the pixels have changed + genID = bitmap->getGenerationID(); + ABitmap_notifyPixelsChanged(abmp); + ASSERT_TRUE(bitmap->getGenerationID() == genID); +} diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 90bcd1c0e370..1208062d9da0 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -403,3 +403,40 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); } + +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { + // create a pipeline and add a picture callback + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + int callbackCount = 0; + pipeline->setPictureCapturedCallback( + [&callbackCount](sk_sp<SkPicture>&& picture) { callbackCount += 1; }); + + // create basic red frame and render it + auto redNode = TestUtils::createSkiaNode( + 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRectMakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + bool opaque = true; + android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); + auto surface = SkSurface::MakeRasterN32Premul(1, 1); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + + // verify the callback was called + EXPECT_EQ(1, callbackCount); + + // render a second frame and check the callback count + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + EXPECT_EQ(2, callbackCount); + + // unset the callback, render another frame, check callback was not invoked + pipeline->setPictureCapturedCallback(nullptr); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + EXPECT_EQ(2, callbackCount); +} diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 415092623531..75ea0cbada92 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -47,10 +47,10 @@ interface ILocationManager Location getLastLocation(in LocationRequest request, String packageName, String featureId); boolean getCurrentLocation(in LocationRequest request, in ICancellationSignal cancellationSignal, in ILocationListener listener, - String packageName, String featureId); + String packageName, String featureId, String listenerId); void requestLocationUpdates(in LocationRequest request, in ILocationListener listener, - in PendingIntent intent, String packageName, String featureId); + in PendingIntent intent, String packageName, String featureId, String listenerId); void removeUpdates(in ILocationListener listener, in PendingIntent intent); void requestGeofence(in LocationRequest request, in Geofence geofence, diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index fcbd3e540291..d1b41dfccf63 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -718,7 +718,7 @@ public class LocationManager { currentLocationRequest.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS); } - GetCurrentLocationTransport listenerTransport = new GetCurrentLocationTransport(executor, + GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor, consumer); if (cancellationSignal != null) { @@ -729,14 +729,15 @@ public class LocationManager { try { if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal, - listenerTransport, mContext.getPackageName(), mContext.getAttributionTag())) { - listenerTransport.register(mContext.getSystemService(AlarmManager.class), + transport, mContext.getPackageName(), mContext.getAttributionTag(), + transport.getListenerId())) { + transport.register(mContext.getSystemService(AlarmManager.class), remoteCancellationSignal); if (cancellationSignal != null) { - cancellationSignal.setOnCancelListener(listenerTransport::cancel); + cancellationSignal.setOnCancelListener(transport::cancel); } } else { - listenerTransport.fail(); + transport.fail(); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1175,7 +1176,8 @@ public class LocationManager { boolean registered = false; try { mService.requestLocationUpdates(locationRequest, transport, null, - mContext.getPackageName(), mContext.getAttributionTag()); + mContext.getPackageName(), mContext.getAttributionTag(), + transport.getListenerId()); registered = true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1220,7 +1222,7 @@ public class LocationManager { try { mService.requestLocationUpdates(locationRequest, null, pendingIntent, - mContext.getPackageName(), mContext.getAttributionTag()); + mContext.getPackageName(), mContext.getAttributionTag(), null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2558,6 +2560,10 @@ public class LocationManager { mRemoteCancellationSignal = null; } + public String getListenerId() { + return mConsumer.getClass().getName() + "@" + System.identityHashCode(mConsumer); + } + public synchronized void register(AlarmManager alarmManager, ICancellationSignal remoteCancellationSignal) { if (mConsumer == null) { @@ -2683,6 +2689,10 @@ public class LocationManager { return mListener; } + public String getListenerId() { + return mListener.getClass().getName() + "@" + System.identityHashCode(mListener); + } + public void register(@NonNull Executor executor) { Preconditions.checkArgument(executor != null, "invalid null executor"); mExecutor = executor; diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 4dd1a29d8595..5f0acc8f7647 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -264,8 +264,8 @@ public final class LocationRequest implements Parcelable { /* numUpdates= */ Integer.MAX_VALUE, /* smallestDisplacement= */ 0, /* hideFromAppOps= */ false, - /* lowPowerMode= */ false, /* locationSettingsIgnored= */ false, + /* lowPowerMode= */ false, /* workSource= */ null); } @@ -282,8 +282,8 @@ public final class LocationRequest implements Parcelable { src.mNumUpdates, src.mSmallestDisplacement, src.mHideFromAppOps, - src.mLowPowerMode, src.mLocationSettingsIgnored, + src.mLowPowerMode, src.mWorkSource); } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 155eb1cfbd46..c11762bcdb40 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -44,46 +44,56 @@ import java.util.Set; /** * @hide */ +@TestApi public class AudioSystem { private static final boolean DEBUG_VOLUME = false; private static final String TAG = "AudioSystem"; + + // private constructor to prevent instantiating AudioSystem + private AudioSystem() { + throw new UnsupportedOperationException("Trying to instantiate AudioSystem"); + } + /* These values must be kept in sync with system/audio.h */ /* * If these are modified, please also update Settings.System.VOLUME_SETTINGS * and attrs.xml and AudioManager.java. */ - /** Used to identify the default audio stream volume */ + /** @hide Used to identify the default audio stream volume */ + @TestApi public static final int STREAM_DEFAULT = -1; - /** Used to identify the volume of audio streams for phone calls */ + /** @hide Used to identify the volume of audio streams for phone calls */ public static final int STREAM_VOICE_CALL = 0; - /** Used to identify the volume of audio streams for system sounds */ + /** @hide Used to identify the volume of audio streams for system sounds */ public static final int STREAM_SYSTEM = 1; - /** Used to identify the volume of audio streams for the phone ring and message alerts */ + /** @hide Used to identify the volume of audio streams for the phone ring and message alerts */ public static final int STREAM_RING = 2; - /** Used to identify the volume of audio streams for music playback */ + /** @hide Used to identify the volume of audio streams for music playback */ public static final int STREAM_MUSIC = 3; - /** Used to identify the volume of audio streams for alarms */ + /** @hide Used to identify the volume of audio streams for alarms */ public static final int STREAM_ALARM = 4; - /** Used to identify the volume of audio streams for notifications */ + /** @hide Used to identify the volume of audio streams for notifications */ public static final int STREAM_NOTIFICATION = 5; - /** Used to identify the volume of audio streams for phone calls when connected on bluetooth */ + /** @hide + * Used to identify the volume of audio streams for phone calls when connected on bluetooth */ public static final int STREAM_BLUETOOTH_SCO = 6; - /** Used to identify the volume of audio streams for enforced system sounds in certain + /** @hide Used to identify the volume of audio streams for enforced system sounds in certain * countries (e.g camera in Japan) */ @UnsupportedAppUsage public static final int STREAM_SYSTEM_ENFORCED = 7; - /** Used to identify the volume of audio streams for DTMF tones */ + /** @hide Used to identify the volume of audio streams for DTMF tones */ public static final int STREAM_DTMF = 8; - /** Used to identify the volume of audio streams exclusively transmitted through the + /** @hide Used to identify the volume of audio streams exclusively transmitted through the * speaker (TTS) of the device */ public static final int STREAM_TTS = 9; - /** Used to identify the volume of audio streams for accessibility prompts */ + /** @hide Used to identify the volume of audio streams for accessibility prompts */ public static final int STREAM_ACCESSIBILITY = 10; - /** Used to identify the volume of audio streams for virtual assistant */ + /** @hide Used to identify the volume of audio streams for virtual assistant */ public static final int STREAM_ASSISTANT = 11; /** + * @hide * @deprecated Use {@link #numStreamTypes() instead} */ public static final int NUM_STREAMS = 5; @@ -96,9 +106,16 @@ public class AudioSystem // Expose only the getter method publicly so we can change it in the future private static final int NUM_STREAM_TYPES = 12; + + /** + * @hide + * @return total number of stream types + */ @UnsupportedAppUsage + @TestApi public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; } + /** @hide */ public static final String[] STREAM_NAMES = new String[] { "STREAM_VOICE_CALL", "STREAM_SYSTEM", @@ -114,7 +131,8 @@ public class AudioSystem "STREAM_ASSISTANT" }; - /* + /** + * @hide * Sets the microphone mute on or off. * * @param on set <var>true</var> to mute the microphone; @@ -124,7 +142,8 @@ public class AudioSystem @UnsupportedAppUsage public static native int muteMicrophone(boolean on); - /* + /** + * @hide * Checks whether the microphone mute is on or off. * * @return true if microphone is muted, false if it's not @@ -133,15 +152,24 @@ public class AudioSystem public static native boolean isMicrophoneMuted(); /* modes for setPhoneState, must match AudioSystem.h audio_mode */ + /** @hide */ public static final int MODE_INVALID = -2; + /** @hide */ public static final int MODE_CURRENT = -1; + /** @hide */ public static final int MODE_NORMAL = 0; + /** @hide */ public static final int MODE_RINGTONE = 1; + /** @hide */ public static final int MODE_IN_CALL = 2; + /** @hide */ public static final int MODE_IN_COMMUNICATION = 3; + /** @hide */ public static final int MODE_CALL_SCREENING = 4; + /** @hide */ public static final int NUM_MODES = 5; + /** @hide */ public static String modeToString(int mode) { switch (mode) { case MODE_CURRENT: return "MODE_CURRENT"; @@ -156,15 +184,23 @@ public class AudioSystem } /* Formats for A2DP codecs, must match system/audio-base.h audio_format_t */ + /** @hide */ public static final int AUDIO_FORMAT_INVALID = 0xFFFFFFFF; + /** @hide */ public static final int AUDIO_FORMAT_DEFAULT = 0; + /** @hide */ public static final int AUDIO_FORMAT_AAC = 0x04000000; + /** @hide */ public static final int AUDIO_FORMAT_SBC = 0x1F000000; + /** @hide */ public static final int AUDIO_FORMAT_APTX = 0x20000000; + /** @hide */ public static final int AUDIO_FORMAT_APTX_HD = 0x21000000; + /** @hide */ public static final int AUDIO_FORMAT_LDAC = 0x23000000; /** + * @hide * Convert audio format enum values to Bluetooth codec values */ public static int audioFormatToBluetoothSourceCodec(int audioFormat) { @@ -179,25 +215,27 @@ public class AudioSystem } /* Routing bits for the former setRouting/getRouting API */ - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_EARPIECE = (1 << 0); - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_SPEAKER = (1 << 1); - /** @deprecated use {@link #ROUTE_BLUETOOTH_SCO} */ + /** @hide @deprecated use {@link #ROUTE_BLUETOOTH_SCO} */ @Deprecated public static final int ROUTE_BLUETOOTH = (1 << 2); - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_BLUETOOTH_SCO = (1 << 2); - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_HEADSET = (1 << 3); - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_BLUETOOTH_A2DP = (1 << 4); - /** @deprecated */ + /** @hide @deprecated */ @Deprecated public static final int ROUTE_ALL = 0xFFFFFFFF; // Keep in sync with system/media/audio/include/system/audio.h + /** @hide */ public static final int AUDIO_SESSION_ALLOCATE = 0; - /* + /** + * @hide * Checks whether the specified stream type is active. * * return true if any track playing on this stream is active. @@ -205,7 +243,8 @@ public class AudioSystem @UnsupportedAppUsage public static native boolean isStreamActive(int stream, int inPastMs); - /* + /** + * @hide * Checks whether the specified stream type is active on a remotely connected device. The notion * of what constitutes a remote device is enforced by the audio policy manager of the platform. * @@ -213,7 +252,8 @@ public class AudioSystem */ public static native boolean isStreamActiveRemotely(int stream, int inPastMs); - /* + /** + * @hide * Checks whether the specified audio source is active. * * return true if any recorder using this source is currently recording @@ -221,23 +261,27 @@ public class AudioSystem @UnsupportedAppUsage public static native boolean isSourceActive(int source); - /* + /** + * @hide * Returns a new unused audio session ID */ public static native int newAudioSessionId(); - /* + /** + * @hide * Returns a new unused audio player ID */ public static native int newAudioPlayerId(); /** + * @hide * Returns a new unused audio recorder ID */ public static native int newAudioRecorderId(); - /* + /** + * @hide * Sets a group generic audio configuration parameters. The use of these parameters * are platform dependent, see libaudio * @@ -247,7 +291,8 @@ public class AudioSystem @UnsupportedAppUsage public static native int setParameters(String keyValuePairs); - /* + /** + * @hide * Gets a group generic audio configuration parameters. The use of these parameters * are platform dependent, see libaudio * @@ -259,16 +304,16 @@ public class AudioSystem public static native String getParameters(String keys); // These match the enum AudioError in frameworks/base/core/jni/android_media_AudioSystem.cpp - /* Command sucessful or Media server restarted. see ErrorCallback */ + /** @hide Command successful or Media server restarted. see ErrorCallback */ public static final int AUDIO_STATUS_OK = 0; - /* Command failed or unspecified audio error. see ErrorCallback */ + /** @hide Command failed or unspecified audio error. see ErrorCallback */ public static final int AUDIO_STATUS_ERROR = 1; - /* Media server died. see ErrorCallback */ + /** @hide Media server died. see ErrorCallback */ public static final int AUDIO_STATUS_SERVER_DIED = 100; - private static ErrorCallback mErrorCallback; + private static ErrorCallback sErrorCallback; - /* + /** @hide * Handles the audio error callback. */ public interface ErrorCallback @@ -283,7 +328,8 @@ public class AudioSystem void onError(int error); }; - /* + /** + * @hide * Registers a callback to be invoked when an error occurs. * @param cb the callback to run */ @@ -291,7 +337,7 @@ public class AudioSystem public static void setErrorCallback(ErrorCallback cb) { synchronized (AudioSystem.class) { - mErrorCallback = cb; + sErrorCallback = cb; if (cb != null) { cb.onError(checkAudioFlinger()); } @@ -303,8 +349,8 @@ public class AudioSystem { ErrorCallback errorCallback = null; synchronized (AudioSystem.class) { - if (mErrorCallback != null) { - errorCallback = mErrorCallback; + if (sErrorCallback != null) { + errorCallback = sErrorCallback; } } if (errorCallback != null) { @@ -313,6 +359,7 @@ public class AudioSystem } /** + * @hide * Handles events from the audio policy manager about dynamic audio policies * @see android.media.audiopolicy.AudioPolicy */ @@ -326,6 +373,7 @@ public class AudioSystem private static DynamicPolicyCallback sDynPolicyCallback; + /** @hide */ public static void setDynamicPolicyCallback(DynamicPolicyCallback cb) { synchronized (AudioSystem.class) { @@ -355,6 +403,7 @@ public class AudioSystem } /** + * @hide * Handles events from the audio policy manager about recording events * @see android.media.AudioManager.AudioRecordingCallback */ @@ -386,6 +435,7 @@ public class AudioSystem private static AudioRecordingCallback sRecordingCallback; + /** @hide */ public static void setRecordingCallback(AudioRecordingCallback cb) { synchronized (AudioSystem.class) { sRecordingCallback = cb; @@ -434,13 +484,21 @@ public class AudioSystem * Error codes used by public APIs (AudioTrack, AudioRecord, AudioManager ...) * Must be kept in sync with frameworks/base/core/jni/android_media_AudioErrors.h */ + /** @hide */ public static final int SUCCESS = 0; + /** @hide */ public static final int ERROR = -1; + /** @hide */ public static final int BAD_VALUE = -2; + /** @hide */ public static final int INVALID_OPERATION = -3; + /** @hide */ public static final int PERMISSION_DENIED = -4; + /** @hide */ public static final int NO_INIT = -5; + /** @hide */ public static final int DEAD_OBJECT = -6; + /** @hide */ public static final int WOULD_BLOCK = -7; /** @hide */ @@ -458,6 +516,7 @@ public class AudioSystem public @interface AudioSystemError {} /** + * @hide * Convert an int error value to its String value for readability. * Accepted error values are the java AudioSystem errors, matching android_media_AudioErrors.h, * which map onto the native status_t type. @@ -494,74 +553,113 @@ public class AudioSystem // // audio device definitions: must be kept in sync with values in system/core/audio.h // - + /** @hide */ public static final int DEVICE_NONE = 0x0; // reserved bits + /** @hide */ public static final int DEVICE_BIT_IN = 0x80000000; + /** @hide */ public static final int DEVICE_BIT_DEFAULT = 0x40000000; // output devices, be sure to update AudioManager.java also + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_EARPIECE = 0x1; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_SPEAKER = 0x2; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_WIRED_HEADSET = 0x4; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_WIRED_HEADPHONE = 0x8; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_SCO = 0x10; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_A2DP = 0x80; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_AUX_DIGITAL = 0x400; + /** @hide */ public static final int DEVICE_OUT_HDMI = DEVICE_OUT_AUX_DIGITAL; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_USB_DEVICE = 0x4000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_TELEPHONY_TX = 0x10000; + /** @hide */ public static final int DEVICE_OUT_LINE = 0x20000; + /** @hide */ public static final int DEVICE_OUT_HDMI_ARC = 0x40000; + /** @hide */ public static final int DEVICE_OUT_SPDIF = 0x80000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_FM = 0x100000; + /** @hide */ public static final int DEVICE_OUT_AUX_LINE = 0x200000; + /** @hide */ public static final int DEVICE_OUT_SPEAKER_SAFE = 0x400000; + /** @hide */ public static final int DEVICE_OUT_IP = 0x800000; + /** @hide */ public static final int DEVICE_OUT_BUS = 0x1000000; + /** @hide */ public static final int DEVICE_OUT_PROXY = 0x2000000; + /** @hide */ public static final int DEVICE_OUT_USB_HEADSET = 0x4000000; + /** @hide */ public static final int DEVICE_OUT_HEARING_AID = 0x8000000; + /** @hide */ public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; // Deprecated in R because multiple device types are no longer accessed as a bit mask. // Removing this will get lint warning about changing hidden apis. + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_OUT_ALL_USB = (DEVICE_OUT_USB_ACCESSORY | DEVICE_OUT_USB_DEVICE | DEVICE_OUT_USB_HEADSET); + /** @hide */ public static final Set<Integer> DEVICE_OUT_ALL_SET; + /** @hide */ public static final Set<Integer> DEVICE_OUT_ALL_A2DP_SET; + /** @hide */ public static final Set<Integer> DEVICE_OUT_ALL_SCO_SET; + /** @hide */ public static final Set<Integer> DEVICE_OUT_ALL_USB_SET; + /** @hide */ public static final Set<Integer> DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO_SET; + /** @hide */ public static final Set<Integer> DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET; static { DEVICE_OUT_ALL_SET = new HashSet<>(); @@ -621,53 +719,85 @@ public class AudioSystem } // input devices + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_AMBIENT = DEVICE_BIT_IN | 0x2; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_BUILTIN_MIC = DEVICE_BIT_IN | 0x4; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = DEVICE_BIT_IN | 0x8; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_WIRED_HEADSET = DEVICE_BIT_IN | 0x10; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_AUX_DIGITAL = DEVICE_BIT_IN | 0x20; + /** @hide */ public static final int DEVICE_IN_HDMI = DEVICE_IN_AUX_DIGITAL; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_VOICE_CALL = DEVICE_BIT_IN | 0x40; + /** @hide */ public static final int DEVICE_IN_TELEPHONY_RX = DEVICE_IN_VOICE_CALL; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_BACK_MIC = DEVICE_BIT_IN | 0x80; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_REMOTE_SUBMIX = DEVICE_BIT_IN | 0x100; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_ANLG_DOCK_HEADSET = DEVICE_BIT_IN | 0x200; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_DGTL_DOCK_HEADSET = DEVICE_BIT_IN | 0x400; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_USB_ACCESSORY = DEVICE_BIT_IN | 0x800; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_USB_DEVICE = DEVICE_BIT_IN | 0x1000; + /** @hide */ public static final int DEVICE_IN_FM_TUNER = DEVICE_BIT_IN | 0x2000; + /** @hide */ public static final int DEVICE_IN_TV_TUNER = DEVICE_BIT_IN | 0x4000; + /** @hide */ public static final int DEVICE_IN_LINE = DEVICE_BIT_IN | 0x8000; + /** @hide */ public static final int DEVICE_IN_SPDIF = DEVICE_BIT_IN | 0x10000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_BLUETOOTH_A2DP = DEVICE_BIT_IN | 0x20000; + /** @hide */ public static final int DEVICE_IN_LOOPBACK = DEVICE_BIT_IN | 0x40000; + /** @hide */ public static final int DEVICE_IN_IP = DEVICE_BIT_IN | 0x80000; + /** @hide */ public static final int DEVICE_IN_BUS = DEVICE_BIT_IN | 0x100000; + /** @hide */ public static final int DEVICE_IN_PROXY = DEVICE_BIT_IN | 0x1000000; + /** @hide */ public static final int DEVICE_IN_USB_HEADSET = DEVICE_BIT_IN | 0x2000000; + /** @hide */ public static final int DEVICE_IN_BLUETOOTH_BLE = DEVICE_BIT_IN | 0x4000000; + /** @hide */ public static final int DEVICE_IN_HDMI_ARC = DEVICE_BIT_IN | 0x8000000; + /** @hide */ public static final int DEVICE_IN_ECHO_REFERENCE = DEVICE_BIT_IN | 0x10000000; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT; + /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_SET; + /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; + /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_USB_SET; static { DEVICE_IN_ALL_SET = new HashSet<>(); @@ -708,15 +838,19 @@ public class AudioSystem DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); } + /** @hide */ public static final String LEGACY_REMOTE_SUBMIX_ADDRESS = "0"; // device states, must match AudioSystem::device_connection_state + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_STATE_UNAVAILABLE = 0; + /** @hide */ @UnsupportedAppUsage public static final int DEVICE_STATE_AVAILABLE = 1; private static final int NUM_DEVICE_STATES = 1; + /** @hide */ public static String deviceStateToString(int state) { switch (state) { case DEVICE_STATE_UNAVAILABLE: return "DEVICE_STATE_UNAVAILABLE"; @@ -725,63 +859,65 @@ public class AudioSystem } } - public static final String DEVICE_OUT_EARPIECE_NAME = "earpiece"; - public static final String DEVICE_OUT_SPEAKER_NAME = "speaker"; - public static final String DEVICE_OUT_WIRED_HEADSET_NAME = "headset"; - public static final String DEVICE_OUT_WIRED_HEADPHONE_NAME = "headphone"; - public static final String DEVICE_OUT_BLUETOOTH_SCO_NAME = "bt_sco"; - public static final String DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs"; - public static final String DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME = "bt_sco_carkit"; - public static final String DEVICE_OUT_BLUETOOTH_A2DP_NAME = "bt_a2dp"; + /** @hide */ public static final String DEVICE_OUT_EARPIECE_NAME = "earpiece"; + /** @hide */ public static final String DEVICE_OUT_SPEAKER_NAME = "speaker"; + /** @hide */ public static final String DEVICE_OUT_WIRED_HEADSET_NAME = "headset"; + /** @hide */ public static final String DEVICE_OUT_WIRED_HEADPHONE_NAME = "headphone"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_SCO_NAME = "bt_sco"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME = "bt_sco_carkit"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_A2DP_NAME = "bt_a2dp"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME = "bt_a2dp_hp"; - public static final String DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME = "bt_a2dp_spk"; - public static final String DEVICE_OUT_AUX_DIGITAL_NAME = "aux_digital"; - public static final String DEVICE_OUT_HDMI_NAME = "hdmi"; - public static final String DEVICE_OUT_ANLG_DOCK_HEADSET_NAME = "analog_dock"; - public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; - public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory"; - public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device"; - public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; - public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx"; - public static final String DEVICE_OUT_LINE_NAME = "line"; - public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; - public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; - public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; - public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; - public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe"; - public static final String DEVICE_OUT_IP_NAME = "ip"; - public static final String DEVICE_OUT_BUS_NAME = "bus"; - public static final String DEVICE_OUT_PROXY_NAME = "proxy"; - public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset"; - public static final String DEVICE_OUT_HEARING_AID_NAME = "hearing_aid_out"; - - public static final String DEVICE_IN_COMMUNICATION_NAME = "communication"; - public static final String DEVICE_IN_AMBIENT_NAME = "ambient"; - public static final String DEVICE_IN_BUILTIN_MIC_NAME = "mic"; - public static final String DEVICE_IN_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs"; - public static final String DEVICE_IN_WIRED_HEADSET_NAME = "headset"; - public static final String DEVICE_IN_AUX_DIGITAL_NAME = "aux_digital"; - public static final String DEVICE_IN_TELEPHONY_RX_NAME = "telephony_rx"; - public static final String DEVICE_IN_BACK_MIC_NAME = "back_mic"; - public static final String DEVICE_IN_REMOTE_SUBMIX_NAME = "remote_submix"; - public static final String DEVICE_IN_ANLG_DOCK_HEADSET_NAME = "analog_dock"; - public static final String DEVICE_IN_DGTL_DOCK_HEADSET_NAME = "digital_dock"; - public static final String DEVICE_IN_USB_ACCESSORY_NAME = "usb_accessory"; - public static final String DEVICE_IN_USB_DEVICE_NAME = "usb_device"; - public static final String DEVICE_IN_FM_TUNER_NAME = "fm_tuner"; - public static final String DEVICE_IN_TV_TUNER_NAME = "tv_tuner"; - public static final String DEVICE_IN_LINE_NAME = "line"; - public static final String DEVICE_IN_SPDIF_NAME = "spdif"; - public static final String DEVICE_IN_BLUETOOTH_A2DP_NAME = "bt_a2dp"; - public static final String DEVICE_IN_LOOPBACK_NAME = "loopback"; - public static final String DEVICE_IN_IP_NAME = "ip"; - public static final String DEVICE_IN_BUS_NAME = "bus"; - public static final String DEVICE_IN_PROXY_NAME = "proxy"; - public static final String DEVICE_IN_USB_HEADSET_NAME = "usb_headset"; - public static final String DEVICE_IN_BLUETOOTH_BLE_NAME = "bt_ble"; - public static final String DEVICE_IN_ECHO_REFERENCE_NAME = "echo_reference"; - public static final String DEVICE_IN_HDMI_ARC_NAME = "hdmi_arc"; + /** @hide */ public static final String DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME = "bt_a2dp_spk"; + /** @hide */ public static final String DEVICE_OUT_AUX_DIGITAL_NAME = "aux_digital"; + /** @hide */ public static final String DEVICE_OUT_HDMI_NAME = "hdmi"; + /** @hide */ public static final String DEVICE_OUT_ANLG_DOCK_HEADSET_NAME = "analog_dock"; + /** @hide */ public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; + /** @hide */ public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory"; + /** @hide */ public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device"; + /** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; + /** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx"; + /** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line"; + /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; + /** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; + /** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; + /** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; + /** @hide */ public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe"; + /** @hide */ public static final String DEVICE_OUT_IP_NAME = "ip"; + /** @hide */ public static final String DEVICE_OUT_BUS_NAME = "bus"; + /** @hide */ public static final String DEVICE_OUT_PROXY_NAME = "proxy"; + /** @hide */ public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset"; + /** @hide */ public static final String DEVICE_OUT_HEARING_AID_NAME = "hearing_aid_out"; + + /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication"; + /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient"; + /** @hide */ public static final String DEVICE_IN_BUILTIN_MIC_NAME = "mic"; + /** @hide */ public static final String DEVICE_IN_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs"; + /** @hide */ public static final String DEVICE_IN_WIRED_HEADSET_NAME = "headset"; + /** @hide */ public static final String DEVICE_IN_AUX_DIGITAL_NAME = "aux_digital"; + /** @hide */ public static final String DEVICE_IN_TELEPHONY_RX_NAME = "telephony_rx"; + /** @hide */ public static final String DEVICE_IN_BACK_MIC_NAME = "back_mic"; + /** @hide */ public static final String DEVICE_IN_REMOTE_SUBMIX_NAME = "remote_submix"; + /** @hide */ public static final String DEVICE_IN_ANLG_DOCK_HEADSET_NAME = "analog_dock"; + /** @hide */ public static final String DEVICE_IN_DGTL_DOCK_HEADSET_NAME = "digital_dock"; + /** @hide */ public static final String DEVICE_IN_USB_ACCESSORY_NAME = "usb_accessory"; + /** @hide */ public static final String DEVICE_IN_USB_DEVICE_NAME = "usb_device"; + /** @hide */ public static final String DEVICE_IN_FM_TUNER_NAME = "fm_tuner"; + /** @hide */ public static final String DEVICE_IN_TV_TUNER_NAME = "tv_tuner"; + /** @hide */ public static final String DEVICE_IN_LINE_NAME = "line"; + /** @hide */ public static final String DEVICE_IN_SPDIF_NAME = "spdif"; + /** @hide */ public static final String DEVICE_IN_BLUETOOTH_A2DP_NAME = "bt_a2dp"; + /** @hide */ public static final String DEVICE_IN_LOOPBACK_NAME = "loopback"; + /** @hide */ public static final String DEVICE_IN_IP_NAME = "ip"; + /** @hide */ public static final String DEVICE_IN_BUS_NAME = "bus"; + /** @hide */ public static final String DEVICE_IN_PROXY_NAME = "proxy"; + /** @hide */ public static final String DEVICE_IN_USB_HEADSET_NAME = "usb_headset"; + /** @hide */ public static final String DEVICE_IN_BLUETOOTH_BLE_NAME = "bt_ble"; + /** @hide */ public static final String DEVICE_IN_ECHO_REFERENCE_NAME = "echo_reference"; + /** @hide */ public static final String DEVICE_IN_HDMI_ARC_NAME = "hdmi_arc"; + /** @hide */ @UnsupportedAppUsage public static String getOutputDeviceName(int device) { @@ -848,6 +984,7 @@ public class AudioSystem } } + /** @hide */ public static String getInputDeviceName(int device) { switch(device) { @@ -910,6 +1047,7 @@ public class AudioSystem } /** + * @hide * Returns a human readable name for a given device type * @param device a native device type, NOT an AudioDeviceInfo type * @return a string describing the device type @@ -922,35 +1060,31 @@ public class AudioSystem } // phone state, match audio_mode??? - public static final int PHONE_STATE_OFFCALL = 0; - public static final int PHONE_STATE_RINGING = 1; - public static final int PHONE_STATE_INCALL = 2; + /** @hide */ public static final int PHONE_STATE_OFFCALL = 0; + /** @hide */ public static final int PHONE_STATE_RINGING = 1; + /** @hide */ public static final int PHONE_STATE_INCALL = 2; // device categories config for setForceUse, must match audio_policy_forced_cfg_t - @UnsupportedAppUsage - public static final int FORCE_NONE = 0; - public static final int FORCE_SPEAKER = 1; - public static final int FORCE_HEADPHONES = 2; - public static final int FORCE_BT_SCO = 3; - public static final int FORCE_BT_A2DP = 4; - public static final int FORCE_WIRED_ACCESSORY = 5; - @UnsupportedAppUsage - public static final int FORCE_BT_CAR_DOCK = 6; - @UnsupportedAppUsage - public static final int FORCE_BT_DESK_DOCK = 7; - @UnsupportedAppUsage - public static final int FORCE_ANALOG_DOCK = 8; - @UnsupportedAppUsage - public static final int FORCE_DIGITAL_DOCK = 9; - public static final int FORCE_NO_BT_A2DP = 10; - public static final int FORCE_SYSTEM_ENFORCED = 11; - public static final int FORCE_HDMI_SYSTEM_AUDIO_ENFORCED = 12; - public static final int FORCE_ENCODED_SURROUND_NEVER = 13; - public static final int FORCE_ENCODED_SURROUND_ALWAYS = 14; - public static final int FORCE_ENCODED_SURROUND_MANUAL = 15; - public static final int NUM_FORCE_CONFIG = 16; - public static final int FORCE_DEFAULT = FORCE_NONE; + /** @hide */ @UnsupportedAppUsage public static final int FORCE_NONE = 0; + /** @hide */ public static final int FORCE_SPEAKER = 1; + /** @hide */ public static final int FORCE_HEADPHONES = 2; + /** @hide */ public static final int FORCE_BT_SCO = 3; + /** @hide */ public static final int FORCE_BT_A2DP = 4; + /** @hide */ public static final int FORCE_WIRED_ACCESSORY = 5; + /** @hide */ @UnsupportedAppUsage public static final int FORCE_BT_CAR_DOCK = 6; + /** @hide */ @UnsupportedAppUsage public static final int FORCE_BT_DESK_DOCK = 7; + /** @hide */ @UnsupportedAppUsage public static final int FORCE_ANALOG_DOCK = 8; + /** @hide */ @UnsupportedAppUsage public static final int FORCE_DIGITAL_DOCK = 9; + /** @hide */ public static final int FORCE_NO_BT_A2DP = 10; + /** @hide */ public static final int FORCE_SYSTEM_ENFORCED = 11; + /** @hide */ public static final int FORCE_HDMI_SYSTEM_AUDIO_ENFORCED = 12; + /** @hide */ public static final int FORCE_ENCODED_SURROUND_NEVER = 13; + /** @hide */ public static final int FORCE_ENCODED_SURROUND_ALWAYS = 14; + /** @hide */ public static final int FORCE_ENCODED_SURROUND_MANUAL = 15; + /** @hide */ public static final int NUM_FORCE_CONFIG = 16; + /** @hide */ public static final int FORCE_DEFAULT = FORCE_NONE; + /** @hide */ public static String forceUseConfigToString(int config) { switch (config) { case FORCE_NONE: return "FORCE_NONE"; @@ -974,16 +1108,17 @@ public class AudioSystem } // usage for setForceUse, must match audio_policy_force_use_t - public static final int FOR_COMMUNICATION = 0; - public static final int FOR_MEDIA = 1; - public static final int FOR_RECORD = 2; - public static final int FOR_DOCK = 3; - public static final int FOR_SYSTEM = 4; - public static final int FOR_HDMI_SYSTEM_AUDIO = 5; - public static final int FOR_ENCODED_SURROUND = 6; - public static final int FOR_VIBRATE_RINGING = 7; + /** @hide */ public static final int FOR_COMMUNICATION = 0; + /** @hide */ public static final int FOR_MEDIA = 1; + /** @hide */ public static final int FOR_RECORD = 2; + /** @hide */ public static final int FOR_DOCK = 3; + /** @hide */ public static final int FOR_SYSTEM = 4; + /** @hide */ public static final int FOR_HDMI_SYSTEM_AUDIO = 5; + /** @hide */ public static final int FOR_ENCODED_SURROUND = 6; + /** @hide */ public static final int FOR_VIBRATE_RINGING = 7; private static final int NUM_FORCE_USE = 8; + /** @hide */ public static String forceUseUsageToString(int usage) { switch (usage) { case FOR_COMMUNICATION: return "FOR_COMMUNICATION"; @@ -998,7 +1133,7 @@ public class AudioSystem } } - /** Wrapper for native methods called from AudioService */ + /** @hide Wrapper for native methods called from AudioService */ public static int setStreamVolumeIndexAS(int stream, int index, int device) { if (DEBUG_VOLUME) { Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream] @@ -1008,10 +1143,11 @@ public class AudioSystem } // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t - public static final int SYNC_EVENT_NONE = 0; - public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; + /** @hide */ public static final int SYNC_EVENT_NONE = 0; + /** @hide */ public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; /** + * @hide * @return command completion status, one of {@link #AUDIO_STATUS_OK}, * {@link #AUDIO_STATUS_ERROR} or {@link #AUDIO_STATUS_SERVER_DIED} */ @@ -1019,12 +1155,15 @@ public class AudioSystem public static native int setDeviceConnectionState(int device, int state, String device_address, String device_name, int codecFormat); + /** @hide */ @UnsupportedAppUsage public static native int getDeviceConnectionState(int device, String device_address); + /** @hide */ public static native int handleDeviceConfigChange(int device, String device_address, String device_name, int codecFormat); + /** @hide */ @UnsupportedAppUsage public static int setPhoneState(int state) { Log.w(TAG, "Do not use this method! Use AudioManager.setMode() instead."); @@ -1038,14 +1177,18 @@ public class AudioSystem * @return command completion status. */ public static native int setPhoneState(int state, int uid); + /** @hide */ @UnsupportedAppUsage public static native int setForceUse(int usage, int config); + /** @hide */ @UnsupportedAppUsage public static native int getForceUse(int usage); + /** @hide */ @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage private static native int setStreamVolumeIndex(int stream, int index, int device); + /** @hide */ public static native int getStreamVolumeIndex(int stream, int device); /** * @hide @@ -1082,16 +1225,22 @@ public class AudioSystem */ public static native int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attributes); + /** @hide */ public static native int setMasterVolume(float value); + /** @hide */ public static native float getMasterVolume(); + /** @hide */ @UnsupportedAppUsage public static native int setMasterMute(boolean mute); + /** @hide */ @UnsupportedAppUsage public static native boolean getMasterMute(); + /** @hide */ @UnsupportedAppUsage public static native int getDevicesForStream(int stream); /** + * @hide * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)} * Get the audio devices that would be used for the routing of the given audio attributes. * @param attributes the {@link AudioAttributes} for which the routing is being queried @@ -1136,31 +1285,43 @@ public class AudioSystem /** @hide returns master balance value in range -1.f -> 1.f, where 0.f is dead center. */ @TestApi public static native float getMasterBalance(); - /** @hide changes the audio balance of the device. */ + /** @hide Changes the audio balance of the device. */ @TestApi public static native int setMasterBalance(float balance); // helpers for android.media.AudioManager.getProperty(), see description there for meaning + /** @hide */ @UnsupportedAppUsage(trackingBug = 134049522) public static native int getPrimaryOutputSamplingRate(); + /** @hide */ @UnsupportedAppUsage(trackingBug = 134049522) public static native int getPrimaryOutputFrameCount(); + /** @hide */ @UnsupportedAppUsage public static native int getOutputLatency(int stream); + /** @hide */ public static native int setLowRamDevice(boolean isLowRamDevice, long totalMemory); + /** @hide */ @UnsupportedAppUsage public static native int checkAudioFlinger(); + /** @hide */ public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation); + /** @hide */ public static native int createAudioPatch(AudioPatch[] patch, AudioPortConfig[] sources, AudioPortConfig[] sinks); + /** @hide */ public static native int releaseAudioPatch(AudioPatch patch); + /** @hide */ public static native int listAudioPatches(ArrayList<AudioPatch> patches, int[] generation); + /** @hide */ public static native int setAudioPortConfig(AudioPortConfig config); + /** @hide */ public static native int startAudioSource(AudioPortConfig config, AudioAttributes audioAttributes); + /** @hide */ public static native int stopAudioSource(int handle); // declare this instance as having a dynamic policy callback handler @@ -1169,36 +1330,42 @@ public class AudioSystem private static native final void native_register_recording_callback(); // must be kept in sync with value in include/system/audio.h - public static final int AUDIO_HW_SYNC_INVALID = 0; + /** @hide */ public static final int AUDIO_HW_SYNC_INVALID = 0; + /** @hide */ public static native int getAudioHwSyncForSession(int sessionId); + /** @hide */ public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register); - /** see AudioPolicy.setUidDeviceAffinities() */ + /** @hide see AudioPolicy.setUidDeviceAffinities() */ public static native int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses); - /** see AudioPolicy.removeUidDeviceAffinities() */ + /** @hide see AudioPolicy.removeUidDeviceAffinities() */ public static native int removeUidDeviceAffinities(int uid); - /** see AudioPolicy.setUserIdDeviceAffinities() */ + /** @hide see AudioPolicy.setUserIdDeviceAffinities() */ public static native int setUserIdDeviceAffinities(int userId, @NonNull int[] types, @NonNull String[] addresses); - /** see AudioPolicy.removeUserIdDeviceAffinities() */ + /** @hide see AudioPolicy.removeUserIdDeviceAffinities() */ public static native int removeUserIdDeviceAffinities(int userId); + /** @hide */ public static native int systemReady(); + /** @hide */ public static native float getStreamVolumeDB(int stream, int index, int device); /** + * @hide * Communicate supported system usages to audio policy service. */ public static native int setSupportedSystemUsages(int[] systemUsages); /** + * @hide * @see AudioManager#setAllowedCapturePolicy() */ public static native int setAllowedCapturePolicy(int uid, int flags); @@ -1212,45 +1379,57 @@ public class AudioSystem private static native boolean native_is_offload_supported(int encoding, int sampleRate, int channelMask, int channelIndexMask, int streamType); + /** @hide */ public static native int getMicrophones(ArrayList<MicrophoneInfo> microphonesInfo); + /** @hide */ public static native int getSurroundFormats(Map<Integer, Boolean> surroundFormats, boolean reported); /** + * @hide * Returns a list of audio formats (codec) supported on the A2DP offload path. */ public static native int getHwOffloadEncodingFormatsSupportedForA2DP( ArrayList<Integer> formatList); + /** @hide */ public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled); /** + * @hide * Communicate UID of active assistant to audio policy service. */ public static native int setAssistantUid(int uid); + /** + * @hide * Communicate UIDs of active accessibility services to audio policy service. */ public static native int setA11yServicesUids(int[] uids); + /** + * @hide * Communicate UID of current InputMethodService to audio policy service. */ public static native int setCurrentImeUid(int uid); /** + * @hide * @see AudioManager#isHapticPlaybackSupported() */ public static native boolean isHapticPlaybackSupported(); /** + * @hide * Send audio HAL server process pids to native audioserver process for use * when generating audio HAL servers tombstones */ public static native int setAudioHalPids(int[] pids); /** + * @hide * @see AudioManager#isCallScreeningModeSupported() */ public static native boolean isCallScreeningModeSupported(); @@ -1258,6 +1437,7 @@ public class AudioSystem // use case routing by product strategy /** + * @hide * Sets the preferred device to use for a given audio strategy in the audio policy engine * @param strategy the id of the strategy to configure * @param device the device type and address to route to when available @@ -1270,6 +1450,7 @@ public class AudioSystem device.getAddress()); } /** + * @hide * Set device routing per product strategy. * @param strategy the id of the strategy to configure * @param deviceType the native device type, NOT AudioDeviceInfo types @@ -1280,6 +1461,7 @@ public class AudioSystem int strategy, int deviceType, String deviceAddress); /** + * @hide * Remove preferred routing for the strategy * @param strategy the id of the strategy to configure * @return {@link #SUCCESS} if successfully removed @@ -1287,6 +1469,7 @@ public class AudioSystem public static native int removePreferredDeviceForStrategy(int strategy); /** + * @hide * Query previously set preferred device for a strategy * @param strategy the id of the strategy to query for * @param device an array of size 1 that will contain the preferred device, or null if @@ -1300,6 +1483,7 @@ public class AudioSystem // Items shared with audio service /** + * @hide * The delay before playing a sound. This small period exists so the user * can press another key (non-volume keys, too) to have it NOT be audible. * <p> @@ -1308,6 +1492,7 @@ public class AudioSystem public static final int PLAY_SOUND_DELAY = 300; /** + * @hide * Constant to identify a focus stack entry that is used to hold the focus while the phone * is ringing or during a call. Used by com.android.internal.telephony.CallManager when * entering and exiting calls. @@ -1315,6 +1500,7 @@ public class AudioSystem public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; /** + * @hide * @see AudioManager#setVibrateSetting(int, int) */ public static int getValueForVibrateSetting(int existingValue, int vibrateType, @@ -1330,10 +1516,12 @@ public class AudioSystem return existingValue; } + /** @hide */ public static int getDefaultStreamVolume(int streamType) { return DEFAULT_STREAM_VOLUME[streamType]; } + /** @hide */ public static int[] DEFAULT_STREAM_VOLUME = new int[] { 4, // STREAM_VOICE_CALL 7, // STREAM_SYSTEM @@ -1349,20 +1537,22 @@ public class AudioSystem 5, // STREAM_ASSISTANT }; + /** @hide */ public static String streamToString(int stream) { if (stream >= 0 && stream < STREAM_NAMES.length) return STREAM_NAMES[stream]; if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) return "USE_DEFAULT_STREAM_TYPE"; return "UNKNOWN_STREAM_" + stream; } - /** The platform has no specific capabilities */ + /** @hide The platform has no specific capabilities */ public static final int PLATFORM_DEFAULT = 0; - /** The platform is voice call capable (a phone) */ + /** @hide The platform is voice call capable (a phone) */ public static final int PLATFORM_VOICE = 1; - /** The platform is a television or a set-top box */ + /** @hide The platform is a television or a set-top box */ public static final int PLATFORM_TELEVISION = 2; /** + * @hide * Return the platform type that this is running on. One of: * <ul> * <li>{@link #PLATFORM_VOICE}</li> @@ -1392,6 +1582,7 @@ public class AudioSystem } /** + * @hide * Return a set of audio device types from a bit mask audio device type, which may * represent multiple audio device types. * FIXME: Remove this when getting ride of bit mask usage of audio device types. @@ -1409,6 +1600,7 @@ public class AudioSystem } /** + * @hide * Return the intersection of two audio device types collections. */ public static Set<Integer> intersectionAudioDeviceTypes( @@ -1419,12 +1611,14 @@ public class AudioSystem } /** + * @hide * Return true if the audio device types collection only contains the given device type. */ public static boolean isSingleAudioDeviceType(@NonNull Set<Integer> types, int type) { return types.size() == 1 && types.contains(type); } + /** @hide */ public static final int DEFAULT_MUTE_STREAMS_AFFECTED = (1 << STREAM_MUSIC) | (1 << STREAM_RING) | @@ -1434,6 +1628,7 @@ public class AudioSystem (1 << STREAM_BLUETOOTH_SCO); /** + * @hide * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes. * Keep in sync with core/jni/android_media_DeviceCallback.h. */ diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index 88a829546989..540955f3b393 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Bundle; @@ -24,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Objects; /** * MediaMetrics is the Java interface to the MediaMetrics service. @@ -50,6 +52,77 @@ public class MediaMetrics { private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8; /** + * Key interface. + * + * The presence of this {@code Key} interface on an object allows + * it to be used to set metrics. + * + * @param <T> type of value associated with {@code Key}. + */ + public interface Key<T> { + /** + * Returns the internal name of the key. + */ + @NonNull + String getName(); + + /** + * Returns the class type of the associated value. + */ + @NonNull + Class<T> getValueClass(); + } + + /** + * Returns a Key object with the correct interface for MediaMetrics. + * + * @param name The name of the key. + * @param type The class type of the value represented by the key. + * @param <T> The type of value. + * @return a new key interface. + */ + @NonNull + public static <T> Key<T> createKey(@NonNull String name, @NonNull Class<T> type) { + // Implementation specific. + return new Key<T>() { + private final String mName = name; + private final Class<T> mType = type; + + @Override + @NonNull + public String getName() { + return mName; + } + + @Override + @NonNull + public Class<T> getValueClass() { + return mType; + } + + /** + * Return true if the name and the type of two objects are the same. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Key)) { + return false; + } + Key<?> other = (Key<?>) obj; + return mName.equals(other.getName()) && mType.equals(other.getValueClass()); + } + + @Override + public int hashCode() { + return Objects.hash(mName, mType); + } + }; + } + + /** * Item records properties and delivers to the MediaMetrics service * */ @@ -202,6 +275,28 @@ public class MediaMetrics { } /** + * Sets a metrics typed key + * @param key + * @param value + * @param <T> + * @return + */ + @NonNull + public <T> Item set(@NonNull Key<T> key, @Nullable T value) { + if (value instanceof Integer) { + putInt(key.getName(), (int) value); + } else if (value instanceof Long) { + putLong(key.getName(), (long) value); + } else if (value instanceof Double) { + putDouble(key.getName(), (double) value); + } else if (value instanceof String) { + putString(key.getName(), (String) value); + } + // if value is null, etc. no error is raised. + return this; + } + + /** * Sets the property with key to an integer (32 bit) value. * * @param key diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java index 9ce895e25b49..9913d23b3301 100644 --- a/media/java/android/media/tv/tuner/Lnb.java +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -20,12 +20,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.content.Context; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.Tuner.Result; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * LNB (low-noise block downconverter) for satellite tuner. @@ -145,7 +145,8 @@ public class Lnb implements AutoCloseable { int mId; LnbCallback mCallback; - Context mContext; + Executor mExecutor; + private native int nativeSetVoltage(int voltage); private native int nativeSetTone(int tone); @@ -159,10 +160,20 @@ public class Lnb implements AutoCloseable { mId = id; } - void setCallback(@Nullable LnbCallback callback) { + void setCallback(Executor executor, @Nullable LnbCallback callback) { mCallback = callback; - if (mCallback == null) { - return; + mExecutor = executor; + } + + private void onEvent(int eventType) { + if (mExecutor != null && mCallback != null) { + mExecutor.execute(() -> mCallback.onEvent(eventType)); + } + } + + private void onDiseqcMessage(byte[] diseqcMessage) { + if (mExecutor != null && mCallback != null) { + mExecutor.execute(() -> mCallback.onDiseqcMessage(diseqcMessage)); } } @@ -218,6 +229,9 @@ public class Lnb implements AutoCloseable { * Releases the LNB instance. */ public void close() { - nativeClose(); + int res = nativeClose(); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "Failed to close LNB"); + } } } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index cf1f1b509ad6..48aed349c39a 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -45,6 +45,7 @@ import android.media.tv.tuner.frontend.ScanCallback; import android.media.tv.tunerresourcemanager.ResourceClientProfile; import android.media.tv.tunerresourcemanager.TunerDemuxRequest; import android.media.tv.tunerresourcemanager.TunerDescramblerRequest; +import android.media.tv.tunerresourcemanager.TunerFrontendInfo; import android.media.tv.tunerresourcemanager.TunerFrontendRequest; import android.media.tv.tunerresourcemanager.TunerLnbRequest; import android.media.tv.tunerresourcemanager.TunerResourceManager; @@ -256,6 +257,36 @@ public class Tuner implements AutoCloseable { mTunerResourceManager.registerClientProfile( profile, new HandlerExecutor(mHandler), mResourceListener, clientId); mClientId = clientId[0]; + + setFrontendInfoList(); + setLnbIds(); + } + + private void setFrontendInfoList() { + List<Integer> ids = nativeGetFrontendIds(); + if (ids == null) { + return; + } + TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()]; + for (int i = 0; i < ids.size(); i++) { + int id = ids.get(i); + FrontendInfo frontendInfo = nativeGetFrontendInfo(id); + if (frontendInfo == null) { + continue; + } + TunerFrontendInfo tunerFrontendInfo = new TunerFrontendInfo( + id, frontendInfo.getType(), frontendInfo.getExclusiveGroupId()); + infos[i] = tunerFrontendInfo; + } + mTunerResourceManager.setFrontendInfoList(infos); + } + + private void setLnbIds() { + int[] ids = nativeGetLnbIds(); + if (ids == null) { + return; + } + mTunerResourceManager.setLnbInfoList(ids); } /** @@ -358,7 +389,7 @@ public class Tuner implements AutoCloseable { private native Filter nativeOpenFilter(int type, int subType, long bufferSize); private native TimeFilter nativeOpenTimeFilter(); - private native List<Integer> nativeGetLnbIds(); + private native int[] nativeGetLnbIds(); private native Lnb nativeOpenLnbByHandle(int handle); private native Lnb nativeOpenLnbByName(String name); @@ -829,6 +860,9 @@ public class Tuner implements AutoCloseable { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(cb, "LnbCallback must not be null"); checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB); + if (mLnb != null) { + mLnb.setCallback(executor, cb); + } return mLnb; } @@ -847,7 +881,11 @@ public class Tuner implements AutoCloseable { Objects.requireNonNull(name, "LNB name must not be null"); Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(cb, "LnbCallback must not be null"); - return nativeOpenLnbByName(name); + mLnb = nativeOpenLnbByName(name); + if (mLnb != null) { + mLnb.setCallback(executor, cb); + } + return mLnb; } private boolean requestLnb() { @@ -872,12 +910,6 @@ public class Tuner implements AutoCloseable { return nativeOpenTimeFilter(); } - private void onLnbEvent(int eventType) { - if (mHandler != null) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LNB_EVENT, eventType, 0)); - } - } - /** * Opens a Descrambler in tuner. * diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java index 547a4923cf7b..c1589cf3fb1d 100644 --- a/media/java/android/media/tv/tuner/TunerUtils.java +++ b/media/java/android/media/tv/tuner/TunerUtils.java @@ -137,6 +137,8 @@ public final class TunerUtils { msg = ""; } switch (r) { + case Tuner.RESULT_SUCCESS: + return; case Tuner.RESULT_INVALID_ARGUMENT: throw new IllegalArgumentException(msg); case Tuner.RESULT_INVALID_STATE: diff --git a/media/java/android/media/tv/tuner/filter/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java index da77b50bc3e5..93599e65d170 100644 --- a/media/java/android/media/tv/tuner/filter/TimeFilter.java +++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java @@ -83,7 +83,7 @@ public class TimeFilter implements AutoCloseable { @Result public int clearTimestamp() { int res = nativeClearTimestamp(); - if (res == 0) { + if (res == Tuner.RESULT_SUCCESS) { mEnable = false; } return res; diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index 63a71e272e53..2c8899cfca78 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -390,7 +390,7 @@ public class TunerResourceManager { * <li>If no Lnb system can be granted, the API would return false. * <ul> * - * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request. + * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this request. * * @param request {@link TunerLnbRequest} information of the current request. * @param lnbId a one-element array to return the granted Lnb id. @@ -479,7 +479,7 @@ public class TunerResourceManager { * * <p>Client must call this whenever it releases an Lnb. * - * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release. + * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this release. * * @param lnbId the id of the released Tuner Lnb. */ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index fc9b91c76a4f..a31f177d66ab 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -676,8 +676,6 @@ status_t JMediaCodec::getOutputFrame( if (buffer->size() > 0) { std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); if (c2Buffer) { - // asC2Buffer clears internal reference, so set the reference again. - buffer->copy(c2Buffer); switch (c2Buffer->data().type()) { case C2BufferData::LINEAR: { std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; @@ -2526,7 +2524,7 @@ static void android_media_MediaCodec_setAudioPresentation( codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); } -static void android_media_MediaCodec_native_init(JNIEnv *env) { +static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { ScopedLocalRef<jclass> clazz( env, env->FindClass("android/media/MediaCodec")); CHECK(clazz.get() != NULL); @@ -2983,7 +2981,7 @@ static void android_media_MediaCodec_LinearBlock_native_obtain( } static jboolean android_media_MediaCodec_LinearBlock_checkCompatible( - JNIEnv *env, jobjectArray codecNames) { + JNIEnv *env, jclass, jobjectArray codecNames) { std::vector<std::string> names; PopulateNamesVector(env, codecNames, &names); bool isCompatible = false; diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 312e5fe14c37..ac7fe5d0403d 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -155,6 +155,7 @@ struct fields_t { jmethodID onFilterEventID; jmethodID lnbInitID; jmethodID onLnbEventID; + jmethodID onLnbDiseqcMessageID; jmethodID onDvrRecordStatusID; jmethodID onDvrPlaybackStatusID; jmethodID descramblerInitID; @@ -170,19 +171,31 @@ static int IP_V6_LENGTH = 16; namespace android { /////////////// LnbCallback /////////////////////// -LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {} +LnbCallback::LnbCallback(jobject lnbObj, LnbId id) : mId(id) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + mLnb = env->NewWeakGlobalRef(lnbObj); +} Return<void> LnbCallback::onEvent(LnbEventType lnbEventType) { ALOGD("LnbCallback::onEvent, type=%d", lnbEventType); JNIEnv *env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod( - mObject, + mLnb, gFields.onLnbEventID, (jint)lnbEventType); return Void(); } -Return<void> LnbCallback::onDiseqcMessage(const hidl_vec<uint8_t>& /*diseqcMessage*/) { +Return<void> LnbCallback::onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) { ALOGD("LnbCallback::onDiseqcMessage"); + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jbyteArray array = env->NewByteArray(diseqcMessage.size()); + env->SetByteArrayRegion( + array, 0, diseqcMessage.size(), reinterpret_cast<jbyte*>(diseqcMessage[0])); + + env->CallVoidMethod( + mLnb, + gFields.onLnbDiseqcMessageID, + array); return Void(); } @@ -875,6 +888,10 @@ jobject JTuner::openFrontendById(int id) { return NULL; } mFe = fe; + mFeId = id; + if (mDemux != NULL) { + mDemux->setFrontendDataSource(mFeId); + } sp<FrontendCallback> feCb = new FrontendCallback(mObject, id); fe->setCallback(feCb); @@ -1062,43 +1079,40 @@ jobject JTuner::getFrontendInfo(int id) { maxSymbolRate, acquireRange, exclusiveGroupId, statusCaps, jcaps); } -jobject JTuner::getLnbIds() { +jintArray JTuner::getLnbIds() { ALOGD("JTuner::getLnbIds()"); - mTuner->getLnbIds([&](Result, const hidl_vec<FrontendId>& lnbIds) { - mLnbIds = lnbIds; + Result res; + hidl_vec<LnbId> lnbIds; + mTuner->getLnbIds([&](Result r, const hidl_vec<LnbId>& ids) { + lnbIds = ids; + res = r; }); - if (mLnbIds.size() == 0) { + if (res != Result::SUCCESS || mLnbIds.size() == 0) { ALOGW("Lnb isn't available"); return NULL; } + mLnbIds = lnbIds; JNIEnv *env = AndroidRuntime::getJNIEnv(); - jclass arrayListClazz = env->FindClass("java/util/ArrayList"); - jmethodID arrayListAdd = env->GetMethodID(arrayListClazz, "add", "(Ljava/lang/Object;)Z"); - jobject obj = env->NewObject(arrayListClazz, env->GetMethodID(arrayListClazz, "<init>", "()V")); - jclass integerClazz = env->FindClass("java/lang/Integer"); - jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V"); + jintArray ids = env->NewIntArray(mLnbIds.size()); + env->SetIntArrayRegion(ids, 0, mLnbIds.size(), reinterpret_cast<jint*>(&mLnbIds[0])); - for (int i=0; i < mLnbIds.size(); i++) { - jobject idObj = env->NewObject(integerClazz, intInit, mLnbIds[i]); - env->CallBooleanMethod(obj, arrayListAdd, idObj); - } - return obj; + return ids; } jobject JTuner::openLnbById(int id) { sp<ILnb> iLnbSp; - mTuner->openLnbById(id, [&](Result, const sp<ILnb>& lnb) { + Result r; + mTuner->openLnbById(id, [&](Result res, const sp<ILnb>& lnb) { + r = res; iLnbSp = lnb; }); - if (iLnbSp == nullptr) { + if (r != Result::SUCCESS || iLnbSp == nullptr) { ALOGE("Failed to open lnb"); return NULL; } mLnb = iLnbSp; - sp<LnbCallback> lnbCb = new LnbCallback(mObject, id); - mLnb->setCallback(lnbCb); JNIEnv *env = AndroidRuntime::getJNIEnv(); jobject lnbObj = env->NewObject( @@ -1106,6 +1120,9 @@ jobject JTuner::openLnbById(int id) { gFields.lnbInitID, (jint) id); + sp<LnbCallback> lnbCb = new LnbCallback(lnbObj, id); + mLnb->setCallback(lnbCb); + sp<Lnb> lnbSp = new Lnb(iLnbSp, lnbObj); lnbSp->incStrong(lnbObj); env->SetLongField(lnbObj, gFields.lnbContext, (jlong) lnbSp.get()); @@ -1129,14 +1146,15 @@ jobject JTuner::openLnbByName(jstring name) { return NULL; } mLnb = iLnbSp; - sp<LnbCallback> lnbCb = new LnbCallback(mObject, id); - mLnb->setCallback(lnbCb); jobject lnbObj = env->NewObject( env->FindClass("android/media/tv/tuner/Lnb"), gFields.lnbInitID, id); + sp<LnbCallback> lnbCb = new LnbCallback(lnbObj, id); + mLnb->setCallback(lnbCb); + sp<Lnb> lnbSp = new Lnb(iLnbSp, lnbObj); lnbSp->incStrong(lnbObj); env->SetLongField(lnbObj, gFields.lnbContext, (jlong) lnbSp.get()); @@ -1206,12 +1224,21 @@ Result JTuner::openDemux() { return Result::SUCCESS; } Result res; + uint32_t id; + sp<IDemux> demuxSp; mTuner->openDemux([&](Result r, uint32_t demuxId, const sp<IDemux>& demux) { - mDemux = demux; - mDemuxId = demuxId; + demuxSp = demux; + id = demuxId; res = r; ALOGD("open demux, id = %d", demuxId); }); + if (res == Result::SUCCESS) { + mDemux = demuxSp; + mDemuxId = id; + if (mFe != NULL) { + mDemux->setFrontendDataSource(mFeId); + } + } return res; } @@ -2242,8 +2269,6 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { gFields.onFrontendEventID = env->GetMethodID(clazz, "onFrontendEvent", "(I)V"); - gFields.onLnbEventID = env->GetMethodID(clazz, "onLnbEvent", "(I)V"); - jclass frontendClazz = env->FindClass("android/media/tv/tuner/Tuner$Frontend"); gFields.frontendInitID = env->GetMethodID(frontendClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); @@ -2251,6 +2276,8 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { jclass lnbClazz = env->FindClass("android/media/tv/tuner/Lnb"); gFields.lnbContext = env->GetFieldID(lnbClazz, "mNativeContext", "J"); gFields.lnbInitID = env->GetMethodID(lnbClazz, "<init>", "(I)V"); + gFields.onLnbEventID = env->GetMethodID(lnbClazz, "onEvent", "(I)V"); + gFields.onLnbDiseqcMessageID = env->GetMethodID(lnbClazz, "onDiseqcMessage", "([B)V"); jclass filterClazz = env->FindClass("android/media/tv/tuner/filter/Filter"); gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J"); @@ -2375,7 +2402,7 @@ static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thi return tuner->getFrontendInfo(id); } -static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) { +static jintArray android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->getLnbIds(); } @@ -3204,8 +3231,14 @@ static int android_media_tv_Tuner_lnb_send_diseqc_msg(JNIEnv* env, jobject lnb, return (jint) r; } -static int android_media_tv_Tuner_close_lnb(JNIEnv*, jobject) { - return 0; +static int android_media_tv_Tuner_close_lnb(JNIEnv* env, jobject lnb) { + sp<Lnb> lnbSp = getLnb(env, lnb); + Result r = lnbSp->getILnb()->close(); + if (r == Result::SUCCESS) { + lnbSp->decStrong(lnb); + env->SetLongField(lnb, gFields.lnbContext, 0); + } + return (jint) r; } static void android_media_tv_Tuner_dvr_set_fd(JNIEnv *env, jobject dvr, jobject jfd) { @@ -3379,8 +3412,7 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_filter }, { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/filter/TimeFilter;", (void *)android_media_tv_Tuner_open_time_filter }, - { "nativeGetLnbIds", "()Ljava/util/List;", - (void *)android_media_tv_Tuner_get_lnb_ids }, + { "nativeGetLnbIds", "()[I", (void *)android_media_tv_Tuner_get_lnb_ids }, { "nativeOpenLnbByHandle", "(I)Landroid/media/tv/tuner/Lnb;", (void *)android_media_tv_Tuner_open_lnb_by_handle }, { "nativeOpenLnbByName", "(Ljava/lang/String;)Landroid/media/tv/tuner/Lnb;", diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index e6f10b24c840..73fc38dbdec8 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -70,7 +70,7 @@ struct LnbCallback : public ILnbCallback { LnbCallback(jweak tunerObj, LnbId id); virtual Return<void> onEvent(LnbEventType lnbEventType); virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage); - jweak mObject; + jweak mLnb; LnbId mId; }; @@ -179,7 +179,7 @@ struct JTuner : public RefBase { int stopScan(); int setLnb(int id); int setLna(bool enable); - jobject getLnbIds(); + jintArray getLnbIds(); jobject openLnbById(int id); jobject openLnbByName(jstring name); jobject openFilter(DemuxFilterType type, int bufferSize); @@ -199,6 +199,7 @@ private: static sp<ITuner> mTuner; hidl_vec<FrontendId> mFeIds; sp<IFrontend> mFe; + int mFeId; hidl_vec<LnbId> mLnbIds; sp<ILnb> mLnb; sp<IDemux> mDemux; diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 4fef48918c97..0af74c4462a6 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -20,8 +20,6 @@ android:id="@+id/notification_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="invisible" - android:layout_marginBottom="@dimen/navigation_bar_height" android:background="@color/notification_shade_background_color"> <View diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml index bf71396984b8..3b53c6aaeac3 100644 --- a/packages/CarSystemUI/res/layout/notification_panel_container.xml +++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml @@ -18,4 +18,5 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/notification_container" android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_height="match_parent" + android:visibility="invisible"/> diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index 1b0a211b733d..067e359e0e49 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -22,10 +22,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <!-- TODO(b/151617493): replace marginBottom with insets. --> <ViewStub android:id="@+id/notification_panel_stub" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout="@layout/notification_panel_container"/> + android:layout="@layout/notification_panel_container" + android:layout_marginBottom="@dimen/navigation_bar_height"/> <ViewStub android:id="@+id/fullscreen_user_switcher_stub" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 43e2918bd98d..0b56d05cc66f 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -32,6 +32,10 @@ <!-- Disable normal notification rendering; we handle that ourselves --> <bool name="config_renderNotifications">false</bool> + <!-- Whether navigationBar touch events should be consumed before reaching the CarFacetButton \ + when the notification panel is open. --> + <bool name="config_consumeNavigationBarTouchWhenNotificationPanelOpen">false</bool> + <!-- Whether heads-up notifications should be shown when shade is open. --> <bool name="config_enableHeadsUpNotificationWhenNotificationShadeOpen">true</bool> <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up diff --git a/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java index f11eff851aa3..d84b2f958feb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java @@ -28,7 +28,6 @@ import dagger.Module; @Module(includes = { DefaultActivityBinder.class, DefaultBroadcastReceiverBinder.class, - DefaultServiceBinder.class, - CarSystemUIBinder.class}) + DefaultServiceBinder.class}) public class CarComponentBinder { } diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 5547fee0159c..c275536e4d92 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -65,7 +65,7 @@ import dagger.Module; import dagger.Provides; @Module(includes = {DividerModule.class}) -abstract class CarSystemUIModule { +public abstract class CarSystemUIModule { @Singleton @Provides diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java index 7d544c9a9bd7..0e923f7164bb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java @@ -40,6 +40,6 @@ import dagger.Component; CarSystemUIModule.class, CarSystemUIBinder.class }) -interface CarSystemUIRootComponent extends SystemUIRootComponent { +public interface CarSystemUIRootComponent extends SystemUIRootComponent { } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java index b057198d5177..44e43fe9af8c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java @@ -24,7 +24,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; */ public interface CarDeviceProvisionedController extends DeviceProvisionedController { /** - * Returns {@code true} then SUW is in progress for the given user. + * Returns {@code true} when SUW is in progress for the given user. */ boolean isUserSetupInProgress(int user); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java index 9d98479dfeff..d8a894cfa8ba 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -16,9 +16,6 @@ package com.android.systemui.car.notification; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.app.ActivityManager; import android.car.Car; import android.car.drivingstate.CarUxRestrictionsManager; @@ -33,7 +30,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; @@ -54,7 +50,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.window.OverlayViewController; +import com.android.systemui.window.OverlayPanelViewController; import com.android.systemui.window.OverlayViewGlobalStateController; import javax.inject.Inject; @@ -62,39 +58,22 @@ import javax.inject.Singleton; /** View controller for the notification panel. */ @Singleton -public class NotificationPanelViewController extends OverlayViewController { - - // used to calculate how fast to open or close the window - private static final float DEFAULT_FLING_VELOCITY = 0; - // max time a fling animation takes - private static final float FLING_ANIMATION_MAX_TIME = 0.5f; - // acceleration rate for the fling animation - private static final float FLING_SPEED_UP_FACTOR = 0.6f; - - private static final int SWIPE_DOWN_MIN_DISTANCE = 25; - private static final int SWIPE_MAX_OFF_PATH = 75; - private static final int SWIPE_THRESHOLD_VELOCITY = 200; +public class NotificationPanelViewController extends OverlayPanelViewController { + private static final boolean DEBUG = true; private static final String TAG = "NotificationPanelViewController"; private final Context mContext; private final Resources mResources; private final CarServiceProvider mCarServiceProvider; - private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final IStatusBarService mBarService; private final CommandQueue mCommandQueue; private final NotificationDataManager mNotificationDataManager; private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper; private final CarNotificationListener mCarNotificationListener; private final NotificationClickHandlerFactory mNotificationClickHandlerFactory; - private final FlingAnimationUtils mFlingAnimationUtils; private final StatusBarStateController mStatusBarStateController; - private final int mSettleClosePercentage; - - private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; - private float mClosingVelocity = DEFAULT_FLING_VELOCITY; - private float mInitialBackgroundAlpha; private float mBackgroundAlphaDiff; @@ -108,13 +87,7 @@ public class NotificationPanelViewController extends OverlayViewController { private float mFirstTouchDownOnGlassPane; private boolean mNotificationListAtBottomAtTimeOfTouch; private boolean mIsSwipingVerticallyToClose; - private int mPercentageFromBottom; - private boolean mIsNotificationAnimating; private boolean mIsNotificationCardSwiping; - private boolean mPanelExpanded = false; - - private View.OnTouchListener mTopNavBarNotificationTouchListener; - private View.OnTouchListener mNavBarNotificationTouchListener; private OnUnseenCountUpdateListener mUnseenCountUpdateListener; @@ -123,6 +96,7 @@ public class NotificationPanelViewController extends OverlayViewController { Context context, @Main Resources resources, OverlayViewGlobalStateController overlayViewGlobalStateController, + FlingAnimationUtils.Builder flingAnimationUtilsBuilder, /* Other things */ CarServiceProvider carServiceProvider, @@ -135,26 +109,21 @@ public class NotificationPanelViewController extends OverlayViewController { CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper, CarNotificationListener carNotificationListener, NotificationClickHandlerFactory notificationClickHandlerFactory, - FlingAnimationUtils.Builder flingAnimationUtilsBuilder, /* Things that need to be replaced */ StatusBarStateController statusBarStateController ) { - super(R.id.notification_panel_stub, overlayViewGlobalStateController); + super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController, + flingAnimationUtilsBuilder, carDeviceProvisionedController); mContext = context; mResources = resources; mCarServiceProvider = carServiceProvider; - mCarDeviceProvisionedController = carDeviceProvisionedController; mBarService = barService; mCommandQueue = commandQueue; mNotificationDataManager = notificationDataManager; mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper; mCarNotificationListener = carNotificationListener; mNotificationClickHandlerFactory = notificationClickHandlerFactory; - mFlingAnimationUtils = flingAnimationUtilsBuilder - .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME) - .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) - .build(); mStatusBarStateController = statusBarStateController; // Notification background setup. @@ -175,60 +144,6 @@ public class NotificationPanelViewController extends OverlayViewController { + " percentage"); } mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha; - - // Notification Panel param setup - mSettleClosePercentage = mResources.getInteger( - R.integer.notification_settle_close_percentage); - - // Attached to the top navigation bar (i.e. status bar) to detect pull down of the - // notification shade. - GestureDetector openGestureDetector = new GestureDetector(mContext, - new OpenNotificationGestureListener() { - @Override - protected void openNotification() { - animateExpandNotificationsPanel(); - } - }); - - // Attached to the NavBars to close the notification shade - GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext, - new NavBarCloseNotificationGestureListener() { - @Override - protected void close() { - if (mPanelExpanded) { - animateCollapsePanels(); - } - } - }); - - mTopNavBarNotificationTouchListener = (v, event) -> { - if (!isInflated()) { - getOverlayViewGlobalStateController().inflateView(this); - } - if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) { - return true; - } - - boolean consumed = openGestureDetector.onTouchEvent(event); - if (consumed) { - return true; - } - maybeCompleteAnimation(event); - return true; - }; - - mNavBarNotificationTouchListener = - (v, event) -> { - if (!isInflated()) { - return true; - } - boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); - if (consumed) { - return true; - } - maybeCompleteAnimation(event); - return true; - }; } @Override @@ -252,14 +167,13 @@ public class NotificationPanelViewController extends OverlayViewController { private void onNotificationViewInflated() { // Find views. mNotificationView = getLayout().findViewById(R.id.notification_view); - View glassPane = mNotificationView.findViewById(R.id.glass_pane); - mHandleBar = mNotificationView.findViewById(R.id.handle_bar); - mNotificationList = mNotificationView.findViewById(R.id.notifications); + setupHandleBar(); + setupNotificationPanel(); mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> { if (launchResult == ActivityManager.START_TASK_TO_FRONT || launchResult == ActivityManager.START_SUCCESS) { - animateCollapsePanels(); + animateCollapsePanel(); } }); @@ -269,39 +183,52 @@ public class NotificationPanelViewController extends OverlayViewController { mNotificationDataManager.getUnseenNotificationCount()); } }); + mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager); mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory); mNotificationView.setNotificationDataManager(mNotificationDataManager); - mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if (!mNotificationList.canScrollVertically(1)) { - mNotificationListAtBottom = true; - return; - } - mNotificationListAtBottom = false; - mIsSwipingVerticallyToClose = false; - mNotificationListAtBottomAtTimeOfTouch = false; - } + mCarServiceProvider.addListener(car -> { + CarUxRestrictionsManager carUxRestrictionsManager = + (CarUxRestrictionsManager) + car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); + mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager( + carUxRestrictionsManager); + + mNotificationViewController = new NotificationViewController( + mNotificationView, + PreprocessingManager.getInstance(mContext), + mCarNotificationListener, + mCarUxRestrictionManagerWrapper, + mNotificationDataManager); + mNotificationViewController.enable(); }); + } - // Attached to the notification ui to detect close request of the notification shade. + private void setupHandleBar() { + mHandleBar = mNotificationView.findViewById(R.id.handle_bar); + GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext, + new HandleBarCloseGestureListener()); + mHandleBar.setOnTouchListener((v, event) -> { + handleBarCloseNotificationGestureDetector.onTouchEvent(event); + maybeCompleteAnimation(event); + return true; + }); + } + + private void setupNotificationPanel() { + View glassPane = mNotificationView.findViewById(R.id.glass_pane); + mNotificationList = mNotificationView.findViewById(R.id.notifications); GestureDetector closeGestureDetector = new GestureDetector(mContext, - new CloseNotificationGestureListener() { + new CloseGestureListener() { @Override protected void close() { - if (mPanelExpanded) { - animateCollapsePanels(); + if (isPanelExpanded()) { + animateCollapsePanel(); } } }); - // Attached to the Handle bar to close the notification shade - GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext, - new HandleBarCloseNotificationGestureListener()); - // The glass pane is used to view touch events before passed to the notification list. // This allows us to initialize gesture listeners and detect when to close the notifications glassPane.setOnTouchListener((v, event) -> { @@ -320,6 +247,21 @@ public class NotificationPanelViewController extends OverlayViewController { return false; }); + mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + // Check if we can scroll vertically downwards. + if (!mNotificationList.canScrollVertically(/* direction= */ 1)) { + mNotificationListAtBottom = true; + return; + } + mNotificationListAtBottom = false; + mIsSwipingVerticallyToClose = false; + mNotificationListAtBottomAtTimeOfTouch = false; + } + }); + mNotificationList.setOnTouchListener((v, event) -> { mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX()) > SWIPE_MAX_OFF_PATH; @@ -341,19 +283,19 @@ public class NotificationPanelViewController extends OverlayViewController { boolean handled = closeGestureDetector.onTouchEvent(event); boolean isTracking = mIsTracking; - Rect rect = mNotificationView.getClipBounds(); + Rect rect = getLayout().getClipBounds(); float clippedHeight = 0; if (rect != null) { clippedHeight = rect.bottom; } if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP && mIsSwipingVerticallyToClose) { - if (mSettleClosePercentage < mPercentageFromBottom && isTracking) { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); - } else if (clippedHeight != mNotificationView.getHeight() && isTracking) { + if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) { + animatePanel(DEFAULT_FLING_VELOCITY, false); + } else if (clippedHeight != getLayout().getHeight() && isTracking) { // this can be caused when user is at the end of the list and trying to // fling to top of the list by scrolling down. - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); + animatePanel(DEFAULT_FLING_VELOCITY, true); } } @@ -365,28 +307,6 @@ public class NotificationPanelViewController extends OverlayViewController { } return handled || isTracking; }); - - mCarServiceProvider.addListener(car -> { - CarUxRestrictionsManager carUxRestrictionsManager = - (CarUxRestrictionsManager) - car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); - mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager( - carUxRestrictionsManager); - - mNotificationViewController = new NotificationViewController( - mNotificationView, - PreprocessingManager.getInstance(mContext), - mCarNotificationListener, - mCarUxRestrictionManagerWrapper, - mNotificationDataManager); - mNotificationViewController.enable(); - }); - - mHandleBar.setOnTouchListener((v, event) -> { - handleBarCloseNotificationGestureDetector.onTouchEvent(event); - maybeCompleteAnimation(event); - return true; - }); } /** Called when the car power state is changed to ON. */ @@ -397,139 +317,40 @@ public class NotificationPanelViewController extends OverlayViewController { mNotificationDataManager.clearAll(); } - View.OnTouchListener getTopNavBarNotificationTouchListener() { - return mTopNavBarNotificationTouchListener; - } - - View.OnTouchListener getNavBarNotificationTouchListener() { - return mNavBarNotificationTouchListener; - } - - private void maybeCompleteAnimation(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_UP - && mNotificationView.getVisibility() == View.VISIBLE) { - if (mSettleClosePercentage < mPercentageFromBottom) { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); - } else { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); - } - } + @Override + protected boolean shouldAnimateCollapsePanel() { + return true; } - /** - * Animates the notification shade from one position to other. This is used to either open or - * close the notification shade completely with a velocity. If the animation is to close the - * notification shade this method also makes the view invisible after animation ends. - */ - private void animateNotificationPanel(float velocity, boolean isClosing) { - float to = 0; - if (!isClosing) { - to = mNotificationView.getHeight(); - } - - Rect rect = mNotificationView.getClipBounds(); - if (rect != null && rect.bottom != to) { - float from = rect.bottom; - animate(from, to, velocity, isClosing); - return; - } - - // We will only be here if the shade is being opened programmatically or via button when - // height of the layout was not calculated. - ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver(); - notificationTreeObserver.addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - ViewTreeObserver obs = mNotificationView.getViewTreeObserver(); - obs.removeOnGlobalLayoutListener(this); - float to = mNotificationView.getHeight(); - animate(/* from= */ 0, to, velocity, isClosing); - } - }); + @Override + protected void onAnimateCollapsePanel() { + // No op. } - private void animateCollapsePanels() { - if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) { - return; - } - getOverlayViewGlobalStateController().setWindowFocusable(false); - animateNotificationPanel(mClosingVelocity, true); + @Override + protected boolean shouldAnimateExpandPanel() { + return mCommandQueue.panelsEnabled(); } - private void animateExpandNotificationsPanel() { - if (!mCommandQueue.panelsEnabled() - || !mCarDeviceProvisionedController.isCurrentUserFullySetup()) { - return; - } - // scroll to top + @Override + protected void onAnimateExpandPanel() { mNotificationList.scrollToPosition(0); - setPanelVisible(true); - mNotificationView.setVisibility(View.VISIBLE); - animateNotificationPanel(mOpeningVelocity, false); - - setPanelExpanded(true); } - private void animate(float from, float to, float velocity, boolean isClosing) { - if (mIsNotificationAnimating) { - return; - } - mIsNotificationAnimating = true; - mIsTracking = true; - ValueAnimator animator = ValueAnimator.ofFloat(from, to); - animator.addUpdateListener( - animation -> { - float animatedValue = (Float) animation.getAnimatedValue(); - setNotificationViewClipBounds((int) animatedValue); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mIsNotificationAnimating = false; - mIsTracking = false; - mOpeningVelocity = DEFAULT_FLING_VELOCITY; - mClosingVelocity = DEFAULT_FLING_VELOCITY; - if (isClosing) { - setPanelVisible(false); - mNotificationView.setVisibility(View.INVISIBLE); - mNotificationView.setClipBounds(null); - mNotificationViewController.onVisibilityChanged(false); - // let the status bar know that the panel is closed - setPanelExpanded(false); - } else { - mNotificationViewController.onVisibilityChanged(true); - // let the status bar know that the panel is open - mNotificationView.setVisibleNotificationsAsSeen(); - setPanelExpanded(true); - } - } - }); - mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity)); - animator.start(); + @Override + protected void onCollapseAnimationEnd() { + mNotificationViewController.onVisibilityChanged(false); } - /** - * Set the panel view to be visible. - */ - public void setPanelVisible(boolean visible) { - if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) { - getOverlayViewGlobalStateController().setWindowVisible(true); - } - if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) { - getOverlayViewGlobalStateController().setWindowVisible(false); - } - getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - getOverlayViewGlobalStateController().setWindowFocusable(visible); + @Override + protected void onExpandAnimationEnd() { + mNotificationViewController.onVisibilityChanged(true); + mNotificationView.setVisibleNotificationsAsSeen(); } - /** - * Set the panel state to expanded. This will expand or collapse the overlay window if - * necessary. - */ - public void setPanelExpanded(boolean expand) { - mPanelExpanded = expand; + @Override + protected void onPanelExpanded(boolean expand) { + super.onPanelExpanded(expand); if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { if (DEBUG) { @@ -550,19 +371,19 @@ public class NotificationPanelViewController extends OverlayViewController { } } - private void setNotificationViewClipBounds(int height) { - if (height > mNotificationView.getHeight()) { - height = mNotificationView.getHeight(); - } - Rect clipBounds = new Rect(); - clipBounds.set(0, 0, mNotificationView.getWidth(), height); - // Sets the clip region on the notification list view. - mNotificationView.setClipBounds(clipBounds); + @Override + protected void onOpenScrollStart() { + mNotificationList.scrollToPosition(0); + } + + @Override + protected void onScroll(int height) { if (mHandleBar != null) { ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams(); mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin); } + if (mNotificationView.getHeight() > 0) { Drawable background = mNotificationView.getBackground().mutate(); background.setAlpha((int) (getBackgroundAlpha(height) * 255)); @@ -570,6 +391,13 @@ public class NotificationPanelViewController extends OverlayViewController { } } + @Override + protected boolean shouldAllowClosingScroll() { + // Unless the notification list is at the bottom, the panel shouldn't be allowed to + // collapse on scroll. + return mNotificationListAtBottomAtTimeOfTouch; + } + /** * Calculates the alpha value for the background based on how much of the notification * shade is visible to the user. When the notification shade is completely open then @@ -580,25 +408,6 @@ public class NotificationPanelViewController extends OverlayViewController { + ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff); } - private void calculatePercentageFromBottom(float height) { - if (mNotificationView.getHeight() > 0) { - mPercentageFromBottom = (int) Math.abs( - height / mNotificationView.getHeight() * 100); - } - } - - /** Toggles the visibility of the notification panel. */ - public void toggle() { - if (!isInflated()) { - getOverlayViewGlobalStateController().inflateView(this); - } - if (mPanelExpanded) { - animateCollapsePanels(); - } else { - animateExpandNotificationsPanel(); - } - } - /** Sets the unseen count listener. */ public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) { mUnseenCountUpdateListener = listener; @@ -614,154 +423,9 @@ public class NotificationPanelViewController extends OverlayViewController { } /** - * Only responsible for open hooks. Since once the panel opens it covers all elements - * there is no need to merge with close. - */ - private abstract class OpenNotificationGestureListener extends - GestureDetector.SimpleOnGestureListener { - - @Override - public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, - float distanceY) { - - if (mNotificationView.getVisibility() == View.INVISIBLE) { - // when the on-scroll is called for the first time to open. - mNotificationList.scrollToPosition(0); - } - setPanelVisible(true); - mNotificationView.setVisibility(View.VISIBLE); - - // clips the view for the notification shade when the user scrolls to open. - setNotificationViewClipBounds((int) event2.getRawY()); - - // Initially the scroll starts with height being zero. This checks protects from divide - // by zero error. - calculatePercentageFromBottom(event2.getRawY()); - - mIsTracking = true; - return true; - } - - - @Override - public boolean onFling(MotionEvent event1, MotionEvent event2, - float velocityX, float velocityY) { - if (velocityY > SWIPE_THRESHOLD_VELOCITY) { - mOpeningVelocity = velocityY; - openNotification(); - return true; - } - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); - - return false; - } - - protected abstract void openNotification(); - } - - /** - * To be installed on the open panel notification panel - */ - private abstract class CloseNotificationGestureListener extends - GestureDetector.SimpleOnGestureListener { - - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - if (mPanelExpanded) { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); - } - return true; - } - - @Override - public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, - float distanceY) { - // should not clip while scroll to the bottom of the list. - if (!mNotificationListAtBottomAtTimeOfTouch) { - return false; - } - float actualNotificationHeight = - mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY()); - if (actualNotificationHeight > mNotificationView.getHeight()) { - actualNotificationHeight = mNotificationView.getHeight(); - } - if (mNotificationView.getHeight() > 0) { - mPercentageFromBottom = (int) Math.abs( - actualNotificationHeight / mNotificationView.getHeight() * 100); - boolean isUp = distanceY > 0; - - // This check is to figure out if onScroll was called while swiping the card at - // bottom of the list. At that time we should not allow notification shade to - // close. We are also checking for the upwards swipe gesture here because it is - // possible if a user is closing the notification shade and while swiping starts - // to open again but does not fling. At that time we should allow the - // notification shade to close fully or else it would stuck in between. - if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight) - > SWIPE_DOWN_MIN_DISTANCE && isUp) { - setNotificationViewClipBounds((int) actualNotificationHeight); - mIsTracking = true; - } else if (!isUp) { - setNotificationViewClipBounds((int) actualNotificationHeight); - } - } - // if we return true the items in RV won't be scrollable. - return false; - } - - - @Override - public boolean onFling(MotionEvent event1, MotionEvent event2, - float velocityX, float velocityY) { - // should not fling if the touch does not start when view is at the bottom of the list. - if (!mNotificationListAtBottomAtTimeOfTouch) { - return false; - } - if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH - || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { - // swipe was not vertical or was not fast enough - return false; - } - boolean isUp = velocityY < 0; - if (isUp) { - close(); - return true; - } else { - // we should close the shade - animateNotificationPanel(velocityY, false); - } - return false; - } - - protected abstract void close(); - } - - /** - * To be installed on the nav bars. - */ - private abstract class NavBarCloseNotificationGestureListener extends - CloseNotificationGestureListener { - @Override - public boolean onSingleTapUp(MotionEvent e) { - mClosingVelocity = DEFAULT_FLING_VELOCITY; - if (mPanelExpanded) { - close(); - } - return super.onSingleTapUp(e); - } - - @Override - public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, - float distanceY) { - calculatePercentageFromBottom(event2.getRawY()); - setNotificationViewClipBounds((int) event2.getRawY()); - return true; - } - } - - /** * To be installed on the handle bar. */ - private class HandleBarCloseNotificationGestureListener extends + private class HandleBarCloseGestureListener extends GestureDetector.SimpleOnGestureListener { @Override @@ -772,9 +436,8 @@ public class NotificationPanelViewController extends OverlayViewController { // the handle bar we should calculate the height using the diff of event1 and event2. // This will help the notification shade to clip smoothly as the event2 value changes // as event1 value will be fixed. - int clipHeight = - mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY()); - setNotificationViewClipBounds(clipHeight); + int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY()); + setViewClipBounds(clipHeight); return true; } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java index 1cfc83293acb..9d71797794b8 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java @@ -20,7 +20,6 @@ import android.car.hardware.power.CarPowerManager; import android.content.res.Configuration; import com.android.systemui.car.CarDeviceProvisionedController; -import com.android.systemui.car.CarServiceProvider; import com.android.systemui.navigationbar.car.CarNavigationBarController; import com.android.systemui.statusbar.car.PowerManagerHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -36,7 +35,6 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, private final CarNavigationBarController mCarNavigationBarController; private final NotificationPanelViewController mNotificationPanelViewController; - private final CarServiceProvider mCarServiceProvider; private final PowerManagerHelper mPowerManagerHelper; private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final ConfigurationController mConfigurationController; @@ -46,7 +44,6 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, CarNavigationBarController carNavigationBarController, NotificationPanelViewController notificationPanelViewController, - CarServiceProvider carServiceProvider, PowerManagerHelper powerManagerHelper, CarDeviceProvisionedController carDeviceProvisionedController, @@ -54,7 +51,6 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, ) { mCarNavigationBarController = carNavigationBarController; mNotificationPanelViewController = notificationPanelViewController; - mCarServiceProvider = carServiceProvider; mPowerManagerHelper = powerManagerHelper; mCarDeviceProvisionedController = carDeviceProvisionedController; mConfigurationController = configurationController; @@ -63,16 +59,26 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, @Override public void registerListeners() { mCarNavigationBarController.registerTopBarTouchListener( - mNotificationPanelViewController.getTopNavBarNotificationTouchListener()); + mNotificationPanelViewController.getDragOpenTouchListener()); mCarNavigationBarController.registerBottomBarTouchListener( - mNotificationPanelViewController.getNavBarNotificationTouchListener()); + mNotificationPanelViewController.getDragCloseTouchListener()); mCarNavigationBarController.registerLeftBarTouchListener( - mNotificationPanelViewController.getNavBarNotificationTouchListener()); + mNotificationPanelViewController.getDragCloseTouchListener()); mCarNavigationBarController.registerRightBarTouchListener( - mNotificationPanelViewController.getNavBarNotificationTouchListener()); + mNotificationPanelViewController.getDragCloseTouchListener()); mCarNavigationBarController.registerNotificationController( - () -> mNotificationPanelViewController.toggle()); + new CarNavigationBarController.NotificationsShadeController() { + @Override + public void togglePanel() { + mNotificationPanelViewController.toggle(); + } + + @Override + public boolean isNotificationPanelOpen() { + return mNotificationPanelViewController.isPanelExpanded(); + } + }); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java deleted file mode 100644 index 597716569e9b..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.car.userswitcher; - -import android.app.admin.DevicePolicyManager; -import android.bluetooth.BluetoothAdapter; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.os.Handler; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * A helper class displays an unlock dialog and receives broadcast about detecting trusted device - * & unlocking state to show the appropriate message on the dialog. - */ -@Singleton -class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{ - private static final String TAG = CarTrustAgentUnlockDialogHelper.class.getSimpleName(); - - private final Context mContext; - private final Resources mResources; - private final WindowManager mWindowManager; - private final UserManager mUserManager; - private final WindowManager.LayoutParams mParams; - /** - * Not using Dialog because context passed from {@link FullscreenUserSwitcherViewMediator} - * is not an activity. - */ - private final View mUnlockDialogLayout; - private final TextView mUnlockingText; - private final Button mButton; - private final IntentFilter mFilter; - private int mUid; - private boolean mIsDialogShowing; - private OnHideListener mOnHideListener; - - @Inject - CarTrustAgentUnlockDialogHelper(Context context, @Main Resources resources, - UserManager userManager, WindowManager windowManager) { - mContext = context; - mResources = resources; - mUserManager = userManager; - mWindowManager = windowManager; - mParams = createLayoutParams(); - mFilter = getIntentFilter(); - - mParams.packageName = mContext.getPackageName(); - mParams.setTitle(mContext.getString(R.string.unlock_dialog_title)); - - mUnlockDialogLayout = LayoutInflater.from(mContext).inflate( - R.layout.trust_agent_unlock_dialog, null); - mUnlockDialogLayout.setLayoutParams(mParams); - - View dialogParent = mUnlockDialogLayout.findViewById(R.id.unlock_dialog_parent); - dialogParent.setOnTouchListener((v, event)-> { - hideUnlockDialog(/* dismissUserSwitcher= */ false); - return true; - }); - View unlockDialog = mUnlockDialogLayout.findViewById(R.id.unlock_dialog); - unlockDialog.setOnTouchListener((v, event) -> { - // If the person taps inside the unlock dialog, the touch event will be intercepted here - // and the dialog will not exit - return true; - }); - mUnlockingText = mUnlockDialogLayout.findViewById(R.id.unlocking_text); - mButton = mUnlockDialogLayout.findViewById(R.id.enter_pin_button); - mButton.setOnClickListener(v -> { - hideUnlockDialog(/* dismissUserSwitcher= */true); - // TODO(b/138250105) Stop unlock advertising - }); - - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (bluetoothAdapter != null - && bluetoothAdapter.getLeState() == BluetoothAdapter.STATE_BLE_ON) { - mUnlockingText.setText(R.string.unlock_dialog_message_start); - } - } - - /** - * This filter is listening on: - * {@link BluetoothAdapter#ACTION_BLE_STATE_CHANGED} for starting unlock advertising; - * {@link Intent#ACTION_USER_UNLOCKED} for IHU unlocked - */ - private IntentFilter getIntentFilter() { - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - return filter; - } - - /** - * Show dialog for the given user - */ - void showUnlockDialog(int uid, OnHideListener listener) { - showUnlockDialogAfterDelay(uid, 0, listener); - } - - /** - * Show dialog for the given user after the certain time of delay has elapsed - * - * @param uid the user to unlock - * @param listener listener that listens to dialog hide - */ - void showUnlockDialogAfterDelay(int uid, OnHideListener listener) { - long delayMillis = mResources.getInteger(R.integer.unlock_dialog_delay_ms); - showUnlockDialogAfterDelay(uid, delayMillis, listener); - } - - /** - * Show dialog for the given user after the supplied delay has elapsed - */ - private void showUnlockDialogAfterDelay(int uid, long delayMillis, OnHideListener listener) { - setUid(uid); - mOnHideListener = listener; - if (!mIsDialogShowing) { - logd("Receiver registered"); - mContext.registerReceiverAsUser(this, UserHandle.ALL, mFilter, - /* broadcastPermission= */ null, - /* scheduler= */ null); - new Handler().postDelayed(() -> { - if (!mUserManager.isUserUnlocked(uid)) { - logd("Showed unlock dialog for user: " + uid + " after " + delayMillis - + " delay."); - mWindowManager.addView(mUnlockDialogLayout, mParams); - } - }, delayMillis); - } - mIsDialogShowing = true; - } - - private void setUid(int uid) { - mUid = uid; - TextView userName = mUnlockDialogLayout.findViewById(R.id.user_name); - userName.setText(mUserManager.getUserInfo(mUid).name); - ImageView avatar = mUnlockDialogLayout.findViewById(R.id.avatar); - avatar.setImageBitmap(mUserManager.getUserIcon(mUid)); - setButtonText(); - } - - private void hideUnlockDialog(boolean dismissUserSwitcher) { - if (!mIsDialogShowing) { - return; - } - mWindowManager.removeView(mUnlockDialogLayout); - logd("Receiver unregistered"); - mContext.unregisterReceiver(this); - if (mOnHideListener != null) { - mOnHideListener.onHide(dismissUserSwitcher); - } - mIsDialogShowing = false; - } - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action == null) { - return; - } - switch (action) { - case BluetoothAdapter.ACTION_BLE_STATE_CHANGED: - logd("Received ACTION_BLE_STATE_CHANGED"); - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - if (state == BluetoothAdapter.STATE_BLE_ON) { - logd("Received BLE_ON"); - mUnlockingText.setText(R.string.unlock_dialog_message_start); - } - break; - case Intent.ACTION_USER_UNLOCKED: - int uid = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (uid == mUid) { - logd("IHU unlocked"); - hideUnlockDialog(/* notifyOnHideListener= */false); - } else { - Log.e(TAG, "Received ACTION_USER_UNLOCKED for unexpected uid: " + uid); - } - break; - default: - Log.e(TAG, "Encountered unexpected action when attempting to set " - + "unlock state message: " + action); - } - } - - // Set button text based on screen lock type - private void setButtonText() { - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid); - switch (passwordQuality) { - // PIN - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: - mButton.setText(R.string.unlock_dialog_button_text_pin); - break; - // Pattern - case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: - mButton.setText(R.string.unlock_dialog_button_text_pattern); - break; - // Password - case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: - case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: - mButton.setText(R.string.unlock_dialog_button_text_password); - break; - default: - Log.e(TAG, "Encountered unexpected screen lock type when attempting to set " - + "button text:" + passwordQuality); - } - } - - private WindowManager.LayoutParams createLayoutParams() { - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, - WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS - | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, - PixelFormat.TRANSLUCENT - ); - attrs.setFitInsetsTypes(0 /* types */); - return attrs; - } - - private void logd(String message) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, message); - } - } - - /** - * Listener used to notify when the dialog is hidden - */ - interface OnHideListener { - void onHide(boolean dismissUserSwitcher); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java index 627729768e88..50e43bea65eb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java @@ -16,23 +16,14 @@ package com.android.systemui.car.userswitcher; -import android.car.Car; -import android.car.trust.CarTrustAgentEnrollmentManager; -import android.car.userlib.CarUserManagerHelper; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.UserInfo; import android.content.res.Resources; import android.os.Handler; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; -import com.android.systemui.car.CarServiceProvider; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -52,9 +43,6 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName(); private final Context mContext; - private final UserManager mUserManager; - private final CarServiceProvider mCarServiceProvider; - private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper; private final CarStatusBarKeyguardViewManager mCarStatusBarKeyguardViewManager; private final Handler mMainHandler; private final StatusBarStateController mStatusBarStateController; @@ -62,39 +50,12 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { private final ScreenLifecycle mScreenLifecycle; private final CarStatusBar mCarStatusBar; private final boolean mIsUserSwitcherEnabled; - private final CarUserManagerHelper mCarUserManagerHelper; - - private CarTrustAgentEnrollmentManager mEnrollmentManager; - private UserGridRecyclerView.UserRecord mSelectedUser; - private final CarTrustAgentUnlockDialogHelper.OnHideListener mOnHideListener = - dismissUserSwitcher -> { - if (dismissUserSwitcher) { - dismissUserSwitcher(); - } else { - // Re-draw the parent view, otherwise the unlock dialog will not be removed - // from the screen immediately. - invalidateFullscreenUserSwitcherView(); - } - }; - private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible."); - } - showDialogForInitialUser(); - mContext.unregisterReceiver(mUserUnlockReceiver); - } - }; @Inject public FullscreenUserSwitcherViewMediator( Context context, @Main Resources resources, @Main Handler mainHandler, - UserManager userManager, - CarServiceProvider carServiceProvider, - CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper, CarStatusBarKeyguardViewManager carStatusBarKeyguardViewManager, CarStatusBar carStatusBar, StatusBarStateController statusBarStateController, @@ -105,21 +66,12 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { mIsUserSwitcherEnabled = resources.getBoolean(R.bool.config_enableFullscreenUserSwitcher); mMainHandler = mainHandler; - mUserManager = userManager; - mCarServiceProvider = carServiceProvider; - mCarServiceProvider.addListener( - car -> mEnrollmentManager = (CarTrustAgentEnrollmentManager) car.getCarManager( - Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE)); - - mUnlockDialogHelper = carTrustAgentUnlockDialogHelper; mCarStatusBarKeyguardViewManager = carStatusBarKeyguardViewManager; mCarStatusBar = carStatusBar; mStatusBarStateController = statusBarStateController; mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController; mScreenLifecycle = screenLifecycle; - - mCarUserManagerHelper = new CarUserManagerHelper(mContext); } @Override @@ -127,18 +79,6 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { registerUserSwitcherShowListeners(); registerUserSwitcherHideListeners(); registerHideKeyguardListeners(); - - if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) { - // User0 is unlocked, switched to the initial user - showDialogForInitialUser(); - } else { - // listen to USER_UNLOCKED - mContext.registerReceiverAsUser(mUserUnlockReceiver, - UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM), - new IntentFilter(Intent.ACTION_USER_UNLOCKED), - /* broadcastPermission= */ null, - /* scheduler= */ null); - } } private void registerUserSwitcherShowListeners() { @@ -194,26 +134,16 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { } /** - * Every time user clicks on an item in the switcher, if the clicked user has no trusted - * device, we hide the switcher, either gradually or immediately. - * If the user has trusted device, we show an unlock dialog to notify user the unlock - * state. - * When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher. - * We dismiss the entire keyguard when we hide the switcher if user clicked on the - * foreground user (user we're already logged in as). + * Every time user clicks on an item in the switcher, we hide the switcher. + * + * We dismiss the entire keyguard if user clicked on the foreground user (user we're already + * logged in as). */ private void onUserSelected(UserGridRecyclerView.UserRecord record) { - mSelectedUser = record; - if (record.mInfo != null) { - if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) { - mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener); - return; - } - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id); - } + hide(); + if (record.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) { + mCarStatusBar.dismissKeyguard(); } - dismissUserSwitcher(); } // We automatically dismiss keyguard unless user switcher is being shown above the keyguard. @@ -234,53 +164,6 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator { } } - private boolean hasScreenLock(int uid) { - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - return lockPatternUtils.getCredentialTypeForUser(uid) - != LockPatternUtils.CREDENTIAL_TYPE_NONE; - } - - private boolean hasTrustedDevice(int uid) { - if (mEnrollmentManager == null) { // car service not ready, so it cannot be available. - return false; - } - return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty(); - } - - private void dismissUserSwitcher() { - if (mSelectedUser == null) { - Log.e(TAG, "Request to dismiss user switcher, but no user selected"); - return; - } - if (mSelectedUser.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) { - hide(); - mCarStatusBar.dismissKeyguard(); - return; - } - hide(); - } - - private void showDialogForInitialUser() { - int initialUser = mCarUserManagerHelper.getInitialUser(); - UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser); - mSelectedUser = new UserGridRecyclerView.UserRecord(initialUserInfo, - UserGridRecyclerView.UserRecord.FOREGROUND_USER); - - // If the initial user has screen lock and trusted device, display the unlock dialog on the - // keyguard. - if (hasScreenLock(initialUser) && hasTrustedDevice(initialUser)) { - mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser, - mOnHideListener); - } else { - // If no trusted device, dismiss the keyguard. - dismissUserSwitcher(); - } - } - - private void invalidateFullscreenUserSwitcherView() { - mFullScreenUserSwitcherViewController.invalidate(); - } - private void hide() { mFullScreenUserSwitcherViewController.stop(); } diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java index 67e9da429c36..fbcd8787135d 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java @@ -314,6 +314,9 @@ public class CarNavigationBarController { public interface NotificationsShadeController { /** Toggles the visibility of the notifications shade. */ void togglePanel(); + + /** Returns {@code true} if the panel is open. */ + boolean isNotificationPanelOpen(); } private void checkAllBars(boolean isSetUp) { diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java index 28da16932fc4..5b99f53af9f9 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java @@ -35,10 +35,11 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; * in a linear layout. */ public class CarNavigationBarView extends LinearLayout { + private final boolean mConsumeTouchWhenPanelOpen; + private View mNavButtons; private CarNavigationButton mNotificationsButton; private NotificationsShadeController mNotificationsShadeController; - private Context mContext; private View mLockScreenButtons; // used to wire in open/close gestures for notifications private OnTouchListener mStatusBarWindowTouchListener; @@ -46,7 +47,8 @@ public class CarNavigationBarView extends LinearLayout { public CarNavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); - mContext = context; + mConsumeTouchWhenPanelOpen = getResources().getBoolean( + R.bool.config_consumeNavigationBarTouchWhenNotificationPanelOpen); } @Override @@ -77,9 +79,16 @@ public class CarNavigationBarView extends LinearLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mStatusBarWindowTouchListener != null) { + boolean shouldConsumeEvent = mNotificationsShadeController == null ? false + : mNotificationsShadeController.isNotificationPanelOpen(); + // Forward touch events to the status bar window so it can drag // windows if required (Notification shade) mStatusBarWindowTouchListener.onTouch(this, ev); + + if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) { + return true; + } } return super.onInterceptTouchEvent(ev); } diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java new file mode 100644 index 000000000000..58022f12e58c --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.window; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.CallSuper; + +import com.android.systemui.R; +import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.FlingAnimationUtils; + +/** + * The {@link OverlayPanelViewController} provides additional dragging animation capabilities to + * {@link OverlayViewController}. + */ +public abstract class OverlayPanelViewController extends OverlayViewController { + + private static final boolean DEBUG = true; + private static final String TAG = "OverlayPanelViewController"; + + // used to calculate how fast to open or close the window + protected static final float DEFAULT_FLING_VELOCITY = 0; + // max time a fling animation takes + protected static final float FLING_ANIMATION_MAX_TIME = 0.5f; + // acceleration rate for the fling animation + protected static final float FLING_SPEED_UP_FACTOR = 0.6f; + + protected static final int SWIPE_DOWN_MIN_DISTANCE = 25; + protected static final int SWIPE_MAX_OFF_PATH = 75; + protected static final int SWIPE_THRESHOLD_VELOCITY = 200; + + private final FlingAnimationUtils mFlingAnimationUtils; + private final CarDeviceProvisionedController mCarDeviceProvisionedController; + private final View.OnTouchListener mDragOpenTouchListener; + private final View.OnTouchListener mDragCloseTouchListener; + + private final int mSettleClosePercentage; + private int mPercentageFromBottom; + + private boolean mPanelVisible; + private boolean mPanelExpanded; + + private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; + private float mClosingVelocity = DEFAULT_FLING_VELOCITY; + + private boolean mIsAnimating; + private boolean mIsTracking; + + public OverlayPanelViewController( + Context context, + @Main Resources resources, + int stubId, + OverlayViewGlobalStateController overlayViewGlobalStateController, + FlingAnimationUtils.Builder flingAnimationUtilsBuilder, + CarDeviceProvisionedController carDeviceProvisionedController + ) { + super(stubId, overlayViewGlobalStateController); + + mFlingAnimationUtils = flingAnimationUtilsBuilder + .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME) + .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) + .build(); + mCarDeviceProvisionedController = carDeviceProvisionedController; + + mSettleClosePercentage = resources.getInteger( + R.integer.notification_settle_close_percentage); + + // Attached to the top navigation bar (i.e. status bar) to detect pull down of the + // notification shade. + GestureDetector openGestureDetector = new GestureDetector(context, + new OpenGestureListener() { + @Override + protected void open() { + animateExpandPanel(); + } + }); + + // Attached to the NavBars to close the notification shade + GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context, + new SystemBarCloseGestureListener() { + @Override + protected void close() { + if (isPanelExpanded()) { + animateCollapsePanel(); + } + } + }); + + mDragOpenTouchListener = (v, event) -> { + if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) { + return true; + } + if (!isInflated()) { + getOverlayViewGlobalStateController().inflateView(this); + } + + boolean consumed = openGestureDetector.onTouchEvent(event); + if (consumed) { + return true; + } + maybeCompleteAnimation(event); + return true; + }; + + mDragCloseTouchListener = (v, event) -> { + if (!isInflated()) { + return true; + } + boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); + if (consumed) { + return true; + } + maybeCompleteAnimation(event); + return true; + }; + } + + /** Toggles the visibility of the panel. */ + public void toggle() { + if (!isInflated()) { + getOverlayViewGlobalStateController().inflateView(this); + } + if (isPanelExpanded()) { + animateCollapsePanel(); + } else { + animateExpandPanel(); + } + } + + /* ***************************************************************************************** * + * Panel Animation + * ***************************************************************************************** */ + + /** Animates the closing of the panel. */ + protected void animateCollapsePanel() { + if (!shouldAnimateCollapsePanel()) { + return; + } + + if (!isPanelExpanded() || !isPanelVisible()) { + return; + } + + onAnimateCollapsePanel(); + getOverlayViewGlobalStateController().setWindowFocusable(false); + animatePanel(mClosingVelocity, /* isClosing= */ true); + } + + /** Determines whether {@link #animateCollapsePanel()} should collapse the panel. */ + protected abstract boolean shouldAnimateCollapsePanel(); + + /** Called when the panel is beginning to collapse. */ + protected abstract void onAnimateCollapsePanel(); + + /** Animates the expansion of the panel. */ + protected void animateExpandPanel() { + if (!shouldAnimateExpandPanel()) { + return; + } + + if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) { + return; + } + + onAnimateExpandPanel(); + setPanelVisible(true); + animatePanel(mOpeningVelocity, /* isClosing= */ false); + + setPanelExpanded(true); + } + + /** Determines whether {@link #animateExpandPanel()}} should expand the panel. */ + protected abstract boolean shouldAnimateExpandPanel(); + + /** Called when the panel is beginning to expand. */ + protected abstract void onAnimateExpandPanel(); + + /** + * Depending on certain conditions, determines whether to fully expand or collapse the panel. + */ + protected void maybeCompleteAnimation(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_UP + && isPanelVisible()) { + if (mSettleClosePercentage < mPercentageFromBottom) { + animatePanel(DEFAULT_FLING_VELOCITY, false); + } else { + animatePanel(DEFAULT_FLING_VELOCITY, true); + } + } + } + + /** + * Animates the panel from one position to other. This is used to either open or + * close the panel completely with a velocity. If the animation is to close the + * panel this method also makes the view invisible after animation ends. + */ + protected void animatePanel(float velocity, boolean isClosing) { + float to = 0; + if (!isClosing) { + to = getLayout().getHeight(); + } + + Rect rect = getLayout().getClipBounds(); + if (rect != null && rect.bottom != to) { + float from = rect.bottom; + animate(from, to, velocity, isClosing); + return; + } + + // We will only be here if the shade is being opened programmatically or via button when + // height of the layout was not calculated. + ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver(); + notificationTreeObserver.addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + ViewTreeObserver obs = getLayout().getViewTreeObserver(); + obs.removeOnGlobalLayoutListener(this); + float to = getLayout().getHeight(); + animate(/* from= */ 0, to, velocity, isClosing); + } + }); + } + + private void animate(float from, float to, float velocity, boolean isClosing) { + if (mIsAnimating) { + return; + } + mIsAnimating = true; + mIsTracking = true; + ValueAnimator animator = ValueAnimator.ofFloat(from, to); + animator.addUpdateListener( + animation -> { + float animatedValue = (Float) animation.getAnimatedValue(); + setViewClipBounds((int) animatedValue); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mIsAnimating = false; + mIsTracking = false; + mOpeningVelocity = DEFAULT_FLING_VELOCITY; + mClosingVelocity = DEFAULT_FLING_VELOCITY; + if (isClosing) { + setPanelVisible(false); + getLayout().setClipBounds(null); + onCollapseAnimationEnd(); + setPanelExpanded(false); + } else { + onExpandAnimationEnd(); + setPanelExpanded(true); + } + } + }); + getFlingAnimationUtils().apply(animator, from, to, Math.abs(velocity)); + animator.start(); + } + + /** + * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is + * closing. + */ + protected abstract void onCollapseAnimationEnd(); + + /** + * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is + * opening. + */ + protected abstract void onExpandAnimationEnd(); + + /* ***************************************************************************************** * + * Panel Visibility + * ***************************************************************************************** */ + + /** Set the panel view to be visible. */ + protected final void setPanelVisible(boolean visible) { + mPanelVisible = visible; + onPanelVisible(visible); + } + + /** Returns {@code true} if panel is visible. */ + public final boolean isPanelVisible() { + return mPanelVisible; + } + + /** Business logic run when panel visibility is set. */ + @CallSuper + protected void onPanelVisible(boolean visible) { + if (DEBUG) { + Log.e(TAG, "onPanelVisible: " + visible); + } + + if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) { + getOverlayViewGlobalStateController().setWindowVisible(true); + } + if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) { + getOverlayViewGlobalStateController().setWindowVisible(false); + } + getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + getOverlayViewGlobalStateController().setWindowFocusable(visible); + } + + /* ***************************************************************************************** * + * Panel Expansion + * ***************************************************************************************** */ + + /** + * Set the panel state to expanded. This will expand or collapse the overlay window if + * necessary. + */ + protected final void setPanelExpanded(boolean expand) { + mPanelExpanded = expand; + onPanelExpanded(expand); + } + + /** Returns {@code true} if panel is expanded. */ + public final boolean isPanelExpanded() { + return mPanelExpanded; + } + + @CallSuper + protected void onPanelExpanded(boolean expand) { + if (DEBUG) { + Log.e(TAG, "onPanelExpanded: " + expand); + } + } + + /* ***************************************************************************************** * + * Misc + * ***************************************************************************************** */ + + protected void calculatePercentageFromBottom(float height) { + if (getLayout().getHeight() > 0) { + mPercentageFromBottom = (int) Math.abs( + height / getLayout().getHeight() * 100); + } + } + + protected void setViewClipBounds(int height) { + if (height > getLayout().getHeight()) { + height = getLayout().getHeight(); + } + Rect clipBounds = new Rect(); + clipBounds.set(0, 0, getLayout().getWidth(), height); + getLayout().setClipBounds(clipBounds); + onScroll(height); + } + + /** Called while scrolling. */ + protected abstract void onScroll(int height); + + /* ***************************************************************************************** * + * Getters + * ***************************************************************************************** */ + + /** Returns the open touch listener. */ + public final View.OnTouchListener getDragOpenTouchListener() { + return mDragOpenTouchListener; + } + + /** Returns the close touch listener. */ + public final View.OnTouchListener getDragCloseTouchListener() { + return mDragCloseTouchListener; + } + + /** Gets the fling animation utils used for animating this panel. */ + protected final FlingAnimationUtils getFlingAnimationUtils() { + return mFlingAnimationUtils; + } + + /** Returns {@code true} if the panel is currently tracking. */ + protected final boolean isTracking() { + return mIsTracking; + } + + /** Returns {@code true} if the panel is currently animating. */ + protected final boolean isAnimating() { + return mIsAnimating; + } + + /** Returns the percentage of the panel that is open from the bottom. */ + protected final int getPercentageFromBottom() { + return mPercentageFromBottom; + } + + /** Returns the percentage at which we've determined whether to open or close the panel. */ + protected final int getSettleClosePercentage() { + return mSettleClosePercentage; + } + + /* ***************************************************************************************** * + * Gesture Listeners + * ***************************************************************************************** */ + + /** Called when the user is beginning to scroll down the panel. */ + protected abstract void onOpenScrollStart(); + + /** + * Only responsible for open hooks. Since once the panel opens it covers all elements + * there is no need to merge with close. + */ + protected abstract class OpenGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, + float distanceY) { + + if (!isPanelVisible()) { + onOpenScrollStart(); + } + setPanelVisible(true); + + // clips the view for the notification shade when the user scrolls to open. + setViewClipBounds((int) event2.getRawY()); + + // Initially the scroll starts with height being zero. This checks protects from divide + // by zero error. + calculatePercentageFromBottom(event2.getRawY()); + + mIsTracking = true; + return true; + } + + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + if (velocityY > SWIPE_THRESHOLD_VELOCITY) { + mOpeningVelocity = velocityY; + open(); + return true; + } + animatePanel(DEFAULT_FLING_VELOCITY, true); + + return false; + } + + protected abstract void open(); + } + + /** Determines whether the scroll event should allow closing of the panel. */ + protected abstract boolean shouldAllowClosingScroll(); + + protected abstract class CloseGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapUp(MotionEvent motionEvent) { + if (isPanelExpanded()) { + animatePanel(DEFAULT_FLING_VELOCITY, true); + } + return true; + } + + @Override + public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, + float distanceY) { + // should not clip while scroll to the bottom of the list. + if (!shouldAllowClosingScroll()) { + return false; + } + float actualNotificationHeight = + getLayout().getHeight() - (event1.getRawY() - event2.getRawY()); + if (actualNotificationHeight > getLayout().getHeight()) { + actualNotificationHeight = getLayout().getHeight(); + } + if (getLayout().getHeight() > 0) { + mPercentageFromBottom = (int) Math.abs( + actualNotificationHeight / getLayout().getHeight() * 100); + boolean isUp = distanceY > 0; + + // This check is to figure out if onScroll was called while swiping the card at + // bottom of the list. At that time we should not allow notification shade to + // close. We are also checking for the upwards swipe gesture here because it is + // possible if a user is closing the notification shade and while swiping starts + // to open again but does not fling. At that time we should allow the + // notification shade to close fully or else it would stuck in between. + if (Math.abs(getLayout().getHeight() - actualNotificationHeight) + > SWIPE_DOWN_MIN_DISTANCE && isUp) { + setViewClipBounds((int) actualNotificationHeight); + mIsTracking = true; + } else if (!isUp) { + setViewClipBounds((int) actualNotificationHeight); + } + } + // if we return true the items in RV won't be scrollable. + return false; + } + + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + // should not fling if the touch does not start when view is at the bottom of the list. + if (!shouldAllowClosingScroll()) { + return false; + } + if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH + || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { + // swipe was not vertical or was not fast enough + return false; + } + boolean isUp = velocityY < 0; + if (isUp) { + close(); + return true; + } else { + // we should close the shade + animatePanel(velocityY, false); + } + return false; + } + + protected abstract void close(); + } + + protected abstract class SystemBarCloseGestureListener extends CloseGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + mClosingVelocity = DEFAULT_FLING_VELOCITY; + if (isPanelExpanded()) { + close(); + } + return super.onSingleTapUp(e); + } + + @Override + public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, + float distanceY) { + calculatePercentageFromBottom(event2.getRawY()); + setViewClipBounds((int) event2.getRawY()); + return true; + } + } +} diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml new file mode 100644 index 000000000000..b0ca8dc4cd34 --- /dev/null +++ b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml @@ -0,0 +1,58 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<com.android.systemui.navigationbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/system_bar_background" + android:orientation="vertical"> + <!--The 20dp padding is the difference between the background selected icon size and the ripple + that was chosen, thus it's a hack to make it look pretty and not an official margin value--> + <LinearLayout + android:id="@id/nav_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingStart="20dp" + android:paddingEnd="20dp" + android:gravity="center"> + + <com.android.systemui.navigationbar.car.CarNavigationButton + android:id="@+id/home" + style="@style/NavigationBarButton" + systemui:componentNames="com.android.car.carlauncher/.CarLauncher" + systemui:icon="@drawable/car_ic_overview" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end" + systemui:selectedIcon="@drawable/car_ic_overview_selected" + systemui:highlightWhenSelected="true" + /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/lock_screen_nav_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingStart="@dimen/car_keyline_1" + android:paddingEnd="@dimen/car_keyline_1" + android:gravity="center" + android:visibility="gone" + /> + +</com.android.systemui.navigationbar.car.CarNavigationBarView> diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java index f2748b89c95c..76557fda6926 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java @@ -20,13 +20,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; -import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.view.LayoutInflater; import android.view.WindowManager; +import androidx.test.filters.SmallTest; + import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarDeviceProvisionedController; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarViewTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarViewTest.java new file mode 100644 index 000000000000..9e2131c9ccfb --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarViewTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.navigationbar.car; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +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; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class CarNavigationBarViewTest extends SysuiTestCase { + + private CarNavigationBarView mNavBarView; + + @Mock + private CarNavigationBarController.NotificationsShadeController mNotificationsShadeController; + + @Mock + private View.OnTouchListener mNavBarTouchListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() { + getContext().getOrCreateTestableResources().addOverride( + R.bool.config_consumeNavigationBarTouchWhenNotificationPanelOpen, false); + } + + @Test + public void dispatchTouch_shadeOpen_flagOff_doesNotConsumeTouch() { + getContext().getOrCreateTestableResources().addOverride( + R.bool.config_consumeNavigationBarTouchWhenNotificationPanelOpen, false); + when(mNotificationsShadeController.isNotificationPanelOpen()).thenReturn(true); + mNavBarView = (CarNavigationBarView) LayoutInflater.from(getContext()).inflate( + R.layout.car_navigation_bar_view_test, /* root= */ null); + mNavBarView.setNotificationsPanelController(mNotificationsShadeController); + mNavBarView.setStatusBarWindowTouchListener(mNavBarTouchListener); + + boolean consume = mNavBarView.onInterceptTouchEvent( + MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300, + MotionEvent.ACTION_MOVE, mNavBarView.getX(), + mNavBarView.getY(), /* metaState= */ 0)); + + assertThat(consume).isFalse(); + } + + @Test + public void dispatchTouch_shadeOpen_flagOn_consumesTouch() { + getContext().getOrCreateTestableResources().addOverride( + R.bool.config_consumeNavigationBarTouchWhenNotificationPanelOpen, true); + when(mNotificationsShadeController.isNotificationPanelOpen()).thenReturn(true); + mNavBarView = (CarNavigationBarView) LayoutInflater.from(getContext()).inflate( + R.layout.car_navigation_bar_view_test, /* root= */ null); + mNavBarView.setNotificationsPanelController(mNotificationsShadeController); + mNavBarView.setStatusBarWindowTouchListener(mNavBarTouchListener); + + boolean consume = mNavBarView.onInterceptTouchEvent( + MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300, + MotionEvent.ACTION_MOVE, mNavBarView.getX(), + mNavBarView.getY(), /* metaState= */ 0)); + + assertThat(consume).isTrue(); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java new file mode 100644 index 000000000000..04f2d06ca71c --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.window; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.tests.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class OverlayPanelViewControllerTest extends SysuiTestCase { + private TestOverlayPanelViewController mOverlayPanelViewController; + private ViewGroup mBaseLayout; + + @Mock + private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + @Mock + private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; + @Mock + private FlingAnimationUtils mFlingAnimationUtils; + @Mock + private CarDeviceProvisionedController mCarDeviceProvisionedController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.overlay_view_controller_test, /* root= */ null); + + when(mFlingAnimationUtilsBuilder.setMaxLengthSeconds(anyFloat())).thenReturn( + mFlingAnimationUtilsBuilder); + when(mFlingAnimationUtilsBuilder.setSpeedUpFactor(anyFloat())).thenReturn( + mFlingAnimationUtilsBuilder); + when(mFlingAnimationUtilsBuilder.build()).thenReturn(mFlingAnimationUtils); + mOverlayPanelViewController = new TestOverlayPanelViewController( + getContext(), + getContext().getOrCreateTestableResources().getResources(), + R.id.overlay_view_controller_stub, + mOverlayViewGlobalStateController, + mFlingAnimationUtilsBuilder, + mCarDeviceProvisionedController); + } + + @Test + public void toggle_notInflated_inflates() { + assertThat(mOverlayPanelViewController.isInflated()).isFalse(); + + mOverlayPanelViewController.toggle(); + + verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController); + } + + @Test + public void toggle_inflated_doesNotInflate() { + mOverlayPanelViewController.inflate(mBaseLayout); + assertThat(mOverlayPanelViewController.isInflated()).isTrue(); + + mOverlayPanelViewController.toggle(); + + verify(mOverlayViewGlobalStateController, never()).inflateView(mOverlayPanelViewController); + } + + @Test + public void toggle_notExpanded_panelExpands() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setPanelExpanded(false); + + mOverlayPanelViewController.toggle(); + + assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue(); + } + + @Test + public void toggle_expanded_panelCollapses() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setPanelExpanded(true); + + mOverlayPanelViewController.toggle(); + + assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue(); + } + + @Test + public void animateCollapsePanel_shouldNotAnimateCollapsePanel_doesNotCollapse() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateCollapsePanel(false); + + mOverlayPanelViewController.animateCollapsePanel(); + + assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue(); + assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse(); + } + + @Test + public void animateCollapsePanel_isNotExpanded_doesNotCollapse() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateCollapsePanel(true); + mOverlayPanelViewController.setPanelExpanded(false); + + mOverlayPanelViewController.animateCollapsePanel(); + + assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue(); + assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse(); + } + + @Test + public void animateCollapsePanel_isNotVisible_doesNotCollapse() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateCollapsePanel(true); + mOverlayPanelViewController.setPanelExpanded(true); + mOverlayPanelViewController.setPanelVisible(false); + + mOverlayPanelViewController.animateCollapsePanel(); + + assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue(); + assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse(); + } + + @Test + public void animateCollapsePanel_collapses() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateCollapsePanel(true); + mOverlayPanelViewController.setPanelExpanded(true); + mOverlayPanelViewController.setPanelVisible(true); + + mOverlayPanelViewController.animateCollapsePanel(); + + assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isTrue(); + } + + @Test + public void animateCollapsePanel_removesWindowFocus() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateCollapsePanel(true); + mOverlayPanelViewController.setPanelExpanded(true); + mOverlayPanelViewController.setPanelVisible(true); + + mOverlayPanelViewController.animateCollapsePanel(); + + verify(mOverlayViewGlobalStateController).setWindowFocusable(false); + } + + @Test + public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateExpandPanel(false); + + mOverlayPanelViewController.animateExpandPanel(); + + assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue(); + assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse(); + } + + @Test + public void animateExpandPanel_userNotSetup_doesNotExpand() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateExpandPanel(true); + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false); + + mOverlayPanelViewController.animateExpandPanel(); + + assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue(); + assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse(); + } + + @Test + public void animateExpandPanel_expands() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateExpandPanel(true); + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true); + + mOverlayPanelViewController.animateExpandPanel(); + + assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isTrue(); + } + + @Test + public void animateExpandPanel_setsPanelVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateExpandPanel(true); + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true); + + mOverlayPanelViewController.animateExpandPanel(); + + assertThat(mOverlayPanelViewController.isPanelVisible()).isTrue(); + } + + @Test + public void animateExpandPanel_setsPanelExpanded() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setShouldAnimateExpandPanel(true); + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true); + + mOverlayPanelViewController.animateExpandPanel(); + + assertThat(mOverlayPanelViewController.isPanelExpanded()).isTrue(); + } + + @Test + public void setPanelVisible_setTrue_windowNotVisible_setsWindowVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false); + + mOverlayPanelViewController.setPanelVisible(true); + + verify(mOverlayViewGlobalStateController).setWindowVisible(true); + } + + @Test + public void setPanelVisible_setTrue_windowVisible_doesNotSetWindowVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true); + + mOverlayPanelViewController.setPanelVisible(true); + + verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true); + } + + @Test + public void setPanelVisible_setTrue_setLayoutVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.getLayout().setVisibility(View.INVISIBLE); + + mOverlayPanelViewController.setPanelVisible(true); + + assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void setPanelVisible_setTrue_setWindowFocusable() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.setPanelVisible(true); + + verify(mOverlayViewGlobalStateController).setWindowFocusable(true); + } + + @Test + public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true); + + mOverlayPanelViewController.setPanelVisible(false); + + verify(mOverlayViewGlobalStateController).setWindowVisible(false); + } + + @Test + public void setPanelVisible_setFalse_windowNotVisible_doesNotSetWindowNotVisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false); + + mOverlayPanelViewController.setPanelVisible(false); + + verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false); + } + + @Test + public void setPanelVisible_setFalse_setLayoutInvisible() { + mOverlayPanelViewController.inflate(mBaseLayout); + mOverlayPanelViewController.getLayout().setVisibility(View.VISIBLE); + + mOverlayPanelViewController.setPanelVisible(false); + + assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo( + View.INVISIBLE); + } + + @Test + public void setPanelVisible_setFalse_setWindowNotFocusable() { + mOverlayPanelViewController.inflate(mBaseLayout); + + mOverlayPanelViewController.setPanelVisible(false); + + verify(mOverlayViewGlobalStateController).setWindowFocusable(false); + } + + @Test + public void dragOpenTouchListener_isNotInflated_inflatesView() { + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true); + assertThat(mOverlayPanelViewController.isInflated()).isFalse(); + + mOverlayPanelViewController.getDragOpenTouchListener().onTouch(/* v= */ null, + MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300, + MotionEvent.ACTION_MOVE, /* x= */ 0, /* y= */ 0, /* metaState= */ 0)); + + verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController); + } + + private static class TestOverlayPanelViewController extends OverlayPanelViewController { + + private boolean mShouldAnimateCollapsePanel; + private boolean mShouldAnimateExpandPanel; + private boolean mShouldAllowClosingScroll; + + boolean mOnAnimateCollapsePanelCalled; + boolean mAnimateCollapsePanelCalled; + boolean mOnAnimateExpandPanelCalled; + boolean mAnimateExpandPanelCalled; + boolean mOnCollapseAnimationEndCalled; + boolean mOnExpandAnimationEndCalled; + boolean mOnOpenScrollStartEnd; + List<Integer> mOnScrollHeights; + + TestOverlayPanelViewController( + Context context, + Resources resources, + int stubId, + OverlayViewGlobalStateController overlayViewGlobalStateController, + FlingAnimationUtils.Builder flingAnimationUtilsBuilder, + CarDeviceProvisionedController carDeviceProvisionedController) { + super(context, resources, stubId, overlayViewGlobalStateController, + flingAnimationUtilsBuilder, + carDeviceProvisionedController); + + mOnScrollHeights = new ArrayList<>(); + } + + public void setShouldAnimateCollapsePanel(boolean shouldAnimate) { + mShouldAnimateCollapsePanel = shouldAnimate; + } + + @Override + protected boolean shouldAnimateCollapsePanel() { + return mShouldAnimateCollapsePanel; + } + + @Override + protected void animateCollapsePanel() { + super.animateCollapsePanel(); + mAnimateCollapsePanelCalled = true; + } + + @Override + protected void onAnimateCollapsePanel() { + mOnAnimateCollapsePanelCalled = true; + } + + public void setShouldAnimateExpandPanel(boolean shouldAnimate) { + mShouldAnimateExpandPanel = shouldAnimate; + } + + @Override + protected boolean shouldAnimateExpandPanel() { + return mShouldAnimateExpandPanel; + } + + @Override + protected void animateExpandPanel() { + super.animateExpandPanel(); + mAnimateExpandPanelCalled = true; + } + + @Override + protected void onAnimateExpandPanel() { + mOnAnimateExpandPanelCalled = true; + } + + @Override + protected void onCollapseAnimationEnd() { + mOnCollapseAnimationEndCalled = true; + } + + @Override + protected void onExpandAnimationEnd() { + mOnExpandAnimationEndCalled = true; + } + + @Override + protected void onScroll(int height) { + mOnScrollHeights.add(height); + } + + @Override + protected void onOpenScrollStart() { + mOnOpenScrollStartEnd = true; + } + + public void setShouldAllowClosingScroll(boolean shouldAllow) { + mShouldAllowClosingScroll = shouldAllow; + } + + @Override + protected boolean shouldAllowClosingScroll() { + return mShouldAllowClosingScroll; + } + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java index 3be962627f62..331326168ba4 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java @@ -43,7 +43,7 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper @SmallTest public class OverlayViewControllerTest extends SysuiTestCase { - private MockOverlayViewController mOverlayViewController; + private TestOverlayViewController mOverlayViewController; private ViewGroup mBaseLayout; @Mock @@ -56,7 +56,7 @@ public class OverlayViewControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); - mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub, + mOverlayViewController = new TestOverlayViewController(R.id.overlay_view_controller_stub, mOverlayViewGlobalStateController); mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( @@ -130,12 +130,12 @@ public class OverlayViewControllerTest extends SysuiTestCase { assertThat(mOverlayViewController.mHideInternalCalled).isFalse(); } - private static class MockOverlayViewController extends OverlayViewController { + private static class TestOverlayViewController extends OverlayViewController { boolean mOnFinishInflateCalled = false; boolean mShowInternalCalled = false; boolean mHideInternalCalled = false; - MockOverlayViewController(int stubId, + TestOverlayViewController(int stubId, OverlayViewGlobalStateController overlayViewGlobalStateController) { super(stubId, overlayViewGlobalStateController); } diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 1681fdbcef06..126efcc9bbd4 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -206,11 +206,11 @@ <string name="enable_adb" msgid="8072776357237289039">"የUSB አራሚ"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"USB ሲያያዝ የአርም ሁኔታ"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"የዩ ኤስ ቢ ስህተት ማረም ፈቀዳዎችን ይሻሩ"</string> - <string name="enable_adb_wireless" msgid="6973226350963971018">"ገመድ-አልባ ማረም"</string> + <string name="enable_adb_wireless" msgid="6973226350963971018">"ገመድ-አልባ debugging"</string> <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Wi-Fi ሲገናኝ የማረም ሁነታ"</string> <string name="adb_wireless_error" msgid="721958772149779856">"ስህተት"</string> - <string name="adb_wireless_settings" msgid="2295017847215680229">"ገመድ-አልባ ማረም"</string> - <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ ማረምን ያብሩ"</string> + <string name="adb_wireless_settings" msgid="2295017847215680229">"ገመድ-አልባ debugging"</string> + <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ debuggingን ያብሩ"</string> <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"የQR ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string> <string name="adb_pair_method_qrcode_summary" msgid="3729901496856458634">"የQR ኮድ መቃኛን በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string> <string name="adb_pair_method_code_title" msgid="1122590300445142904">"የማጣመሪያ ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string> @@ -300,8 +300,8 @@ <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"የሃርድዌር ማቀላጠፊያን ማስተሳሰርን የሚገኝ ከሆነ ይጠቀሙ"</string> <string name="adb_warning_title" msgid="7708653449506485728">"የUSB ማረሚያ ይፈቀድ?"</string> <string name="adb_warning_message" msgid="8145270656419669221">"የUSB አድስ ለግንባታ አላማ ብቻ የታሰበ ነው። ከኮምፒዩተርህ ወደ መሳሪያህ ውሂብ ለመገልበጥ፣ መሣሪያህ ላይ ያለ ማሳወቂያ መተግበሪያዎችን መጫን፣ እና ማስታወሻ ውሂብ ማንበብ ለመጠቀም ይቻላል።"</string> - <string name="adbwifi_warning_title" msgid="727104571653031865">"ገመድ-አልባ ማረም ይፈቀድ?"</string> - <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ ማረም ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string> + <string name="adbwifi_warning_title" msgid="727104571653031865">"ገመድ-አልባ debugging ይፈቀድ?"</string> + <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ debugging ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string> <string name="adb_keys_warning_message" msgid="2968555274488101220">"የዩ ኤስ ቢ ማረም መዳረሻ ከዚህ ቀደም ፍቃድ ከሰጧቸው ኮምፒውተሮች ላይ ይሻሩ?"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"የግንባታ ቅንብሮችን ፍቀድ?"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"እነዚህ ቅንብሮች የታሰቡት ለግንባታ አጠቃቀም ብቻ ናቸው። መሳሪያህን እና በሱ ላይ ያሉትን መተግበሪያዎች እንዲበለሹ ወይም በትክክል እንዳይሰሩ ሊያደርጉ ይችላሉ።"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index abd1e03590f2..88d4c0304396 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -232,8 +232,7 @@ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"عنوان IP والمنفذ"</string> <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"المسح الضوئي لرمز الاستجابة السريعة"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="8578868049289910131">"إقران الجهاز من خلال شبكة Wi‑Fi عن طريق المسح الضوئي لرمز استجابة سريعة"</string> - <!-- no translation found for adb_wireless_no_network_msg (2365795244718494658) --> - <skip /> + <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"يُرجى الاتصال بشبكة Wi-Fi."</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb، تصحيح الأخطاء، مطور برامج"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"اختصار تقرير الأخطاء"</string> <string name="bugreport_in_power_summary" msgid="1885529649381831775">"عرض زر في قائمة خيارات التشغيل لإعداد تقرير بالأخطاء"</string> @@ -432,8 +431,7 @@ <string name="power_discharge_by" msgid="4113180890060388350">"قد تكفي طاقة البطارية حتى حوالي الساعة <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string> <string name="power_discharge_by_only" msgid="92545648425937000">"قد تكفي طاقة البطارية حتى حوالي الساعة <xliff:g id="TIME">%1$s</xliff:g>."</string> <string name="power_discharge_by_only_short" msgid="5883041507426914446">"حتى <xliff:g id="TIME">%1$s</xliff:g>"</string> - <!-- no translation found for power_suggestion_battery_run_out (6332089307827787087) --> - <skip /> + <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"قد ينفد شحن البطارية قبل <xliff:g id="TIME">%1$s</xliff:g>."</string> <string name="power_remaining_less_than_duration_only" msgid="5802195288324091585">"يتبقى أقل من <xliff:g id="THRESHOLD">%1$s</xliff:g>."</string> <string name="power_remaining_less_than_duration" msgid="1812668275239801236">"يتبقى أقل من <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string> <string name="power_remaining_more_than_subtext" msgid="7919119719242734848">"يتبقى أكثر من <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string> @@ -515,32 +513,21 @@ <string name="media_transfer_this_device_name" msgid="2716555073132169240">"مكبر صوت الهاتف"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"حدثت مشكلة أثناء الاتصال. يُرجى إيقاف الجهاز ثم إعادة تشغيله."</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"جهاز سماعي سلكي"</string> - <!-- no translation found for help_label (3528360748637781274) --> - <skip /> - <!-- no translation found for storage_category (2287342585424631813) --> - <skip /> - <!-- no translation found for shared_data_title (1017034836800864953) --> - <skip /> - <!-- no translation found for shared_data_summary (5516326713822885652) --> - <skip /> + <string name="help_label" msgid="3528360748637781274">"المساعدة والتعليقات"</string> + <string name="storage_category" msgid="2287342585424631813">"مساحة التخزين"</string> + <string name="shared_data_title" msgid="1017034836800864953">"البيانات المشتركة"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"عرض البيانات المشتركة وتعديلها"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ما مِن بيانات مشتركة لهذا المستخدم."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"حدث خطأ أثناء جلب البيانات المشتركة. يُرجى إعادة المحاولة."</string> - <!-- no translation found for blob_id_text (8680078988996308061) --> - <skip /> - <!-- no translation found for blob_expires_text (7882727111491739331) --> - <skip /> + <string name="blob_id_text" msgid="8680078988996308061">"معرّف البيانات المشتركة: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"تنتهي صلاحيتها في <xliff:g id="DATE">%s</xliff:g>."</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"حدث خطأ أثناء حذف البيانات المشتركة."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ما مِن عمليات تأجير مطلوبة لهذه المعلومات المشتركة. هل تريد حذفها؟"</string> - <!-- no translation found for accessor_info_title (8289823651512477787) --> - <skip /> - <!-- no translation found for accessor_no_description_text (7510967452505591456) --> - <skip /> - <!-- no translation found for accessor_expires_text (4625619273236786252) --> - <skip /> - <!-- no translation found for delete_blob_text (2819192607255625697) --> - <skip /> - <!-- no translation found for delete_blob_confirmation_text (7807446938920827280) --> - <skip /> + <string name="accessor_info_title" msgid="8289823651512477787">"التطبيقات التي تشارك البيانات"</string> + <string name="accessor_no_description_text" msgid="7510967452505591456">"لم يوفّر التطبيق وصفًا."</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"تنتهي صلاحية العقد في <xliff:g id="DATE">%s</xliff:g>."</string> + <string name="delete_blob_text" msgid="2819192607255625697">"حذف البيانات المشتركة"</string> + <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"هل فعلاً تريد حذف هذه البيانات المشتركة؟"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"المستخدمون لديهم تطبيقات ومحتوى خاص بهم"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"يمكنك تقييد الدخول إلى التطبيقات والمحتوى من حسابك"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"المستخدم"</string> @@ -560,12 +547,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"معلومات الملف الشخصي"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"قبل أن تتمكن من إنشاء ملف شخصي مقيد، يلزمك إعداد تأمين للشاشة لحماية تطبيقاتك وبياناتك الشخصية."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"تعيين التأمين"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"التبديل إلى <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string> </resources> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index a1c00a385339..73a5e7c031d7 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"প্ৰ\'ফাইলৰ তথ্য"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"আপুনি সীমিত প্ৰ\'ফাইল এটা সৃষ্টি কৰাৰ আগেয়ে, আপোনাৰ ব্যক্তিগত ডেটা আৰু এপবিলাকক সুৰক্ষিত কৰিবলৈ স্ক্ৰীণ লক এটা নিৰ্ধাৰণ কৰিব লাগিব।"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"লক ছেট কৰক"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>লৈ সলনি কৰক"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string> + <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> </resources> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 50451e1126e8..4cb4017fcc06 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profil info"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Məhdudlaşdırılmış profil yaratmadan öncə, Siz tətbiqlərinizi və şəxsi datanızı qorumaq üçün ekran kilidi quraşdırmalısınız."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Kilid ayarlayın"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> adlı istifadəçiyə keçin"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string> </resources> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 0e39d7f99f29..839b85aca0aa 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -544,12 +544,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Podaci o profilu"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Da biste mogli da napravite ograničeni profil, treba da podesite zaključavanje ekrana da biste zaštitili aplikacije i lične podatke."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Podesi zaključavanje"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Pređi na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> </resources> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 328762c5361f..cc0d28645856 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Звесткi профiлю"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Перш чым вы зможаце стварыць профіль з абмежаваннямi, вам трэба наладзіць блакiроўку экрана для абароны сваiх дадаткаў і асабістай інфармацыі."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Усталёўка блакiроўкi"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Пераключыцца на карыстальніка <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string> </resources> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 690cc493ebe5..ef0951c623bf 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Инф. за потр. профил"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Преди да можете да създадете потребителски профил с ограничена функционалност, трябва да настроите заключения екран, за да защитите приложенията и личните си данни."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Задаване на заключване"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Превключване към <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> </resources> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 017936fc7b5e..1cd8eeae5ead 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"প্রোফাইল তথ্য"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"আপনি একটি সীমাবদ্ধযুক্ত প্রোফাইল তৈরি করার আগে, আপনাকে আপনার অ্যাপ্লিকেশন এবং ব্যক্তিগত ডেটা সুরক্ষিত করার জন্য একটি স্ক্রিন লক সেট-আপ করতে হবে।"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"লক সেট করুন"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-এ পাল্টান"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string> + <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> </resources> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index a1b1afc77b06..c72411cc10e6 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -544,8 +544,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Podaci o profilu"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Prije nego vam se omogući kreiranje ograničenog profila, morate postaviti zaključavanje ekrana da biste zaštitili svoje aplikacije i lične podatke."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Postaviti zaključavanje"</string> - <string name="user_switch_to_user" msgid="6975428297154968543">"Prelazak na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string> - <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string> + <string name="user_switch_to_user" msgid="6975428297154968543">"Prebaci na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> </resources> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index a1b2da8124dc..765d44259ce4 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informació de perfil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Per crear un perfil restringit, has de configurar una pantalla de bloqueig per protegir les aplicacions i les dades personals."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Defineix un bloqueig"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Canvia a <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string> </resources> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index bbf29bb00303..ed2db3a730e7 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informace o profilu"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Před vytvořením omezeného profilu je nutné nejprve nastavit zámek obrazovky k ochraně aplikací a dat."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Nastavit zámek"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Přepnout na uživatele <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Host"</string> </resources> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 20b7ae15eae5..f682b15deb98 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profiloplysninger"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Før du kan oprette en begrænset profil, skal du oprette en skærmlås for at beskytte dine apps og personlige data."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurer låseskærmen"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Skift til <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string> </resources> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 1a2898508a7d..7918022e5a37 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profilinformationen"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Vor dem Erstellen eines eingeschränkten Profils musst du eine Displaysperre einrichten, um deine Apps und personenbezogenen Daten zu schützen."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Sperre einrichten"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Zu <xliff:g id="USER_NAME">%s</xliff:g> wechseln"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> </resources> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 9c1842a0d284..b4d7c875bf62 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Πληροφορίες προφίλ"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Προκειμένου να μπορέσετε να δημιουργήσετε ένα περιορισμένο προφίλ, θα πρέπει να δημιουργήσετε ένα κλείδωμα οθόνης για την προστασία των εφαρμογών και των προσωπικών δεδομένων σας."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Ορισμός κλειδώματος"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Εναλλαγή σε <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index cc3cd11c8658..6cb975f799ab 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Datos del perfil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restringido, debes configurar un bloqueo de pantalla que proteja tus aplicaciones y datos personales."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Configurar bloqueo"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> </resources> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index e85187b7443d..d77772364f77 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Información del perfil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restringido, debes configurar una pantalla de bloqueo que proteja tus aplicaciones y datos personales."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> </resources> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index c9bd7a090e51..1fa41596a3ac 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profiili teave"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Enne piiratud profiili loomist peate seadistama lukustusekraani, et oma rakendusi ja isiklikke andmeid kaitsta."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Määra lukk"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Lülita kasutajale <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string> </resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 42c702aa3a8a..2700162afc03 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -235,7 +235,7 @@ <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Konektatu wifi-sare batera"</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, araztu, gailua"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"Akatsen txostenerako lasterbidea"</string> - <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Bateriaren menuan, erakutsi akatsen txostena sortzeko botoia"</string> + <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Pizteko menuan, erakutsi akatsen txostena sortzeko botoia"</string> <string name="keep_screen_on" msgid="1187161672348797558">"Mantendu aktibo"</string> <string name="keep_screen_on_summary" msgid="1510731514101925829">"Pantaila ez da ezarriko inoiz inaktibo kargatu bitartean"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Gaitu Bluetooth HCI miatze-erregistroa"</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profileko informazioa"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Profil murriztua sortu aurretik, aplikazioak eta datu pertsonalak babesteko, pantaila blokeatzeko metodo bat konfiguratu beharko duzu."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Ezarri blokeoa"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Aldatu <xliff:g id="USER_NAME">%s</xliff:g> erabiltzailera"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string> </resources> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 2a284ee2e6ce..1c32926772f9 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"اطلاعات نمایه"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"قبل از ایجاد یک نمایه محدود، باید یک قفل صفحه را برای محافظت از برنامهها و دادههای شخصی خود تنظیم کنید."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"تنظیم قفل"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"رفتن به <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string> + <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string> </resources> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 76e8f5b72e4d..765e08edd552 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profiilin tiedot"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Ennen kuin voit luoda rajoitetun profiilin, määritä näytön lukitus, joka suojelee sovelluksiasi ja henkilökohtaisia tietojasi."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Aseta lukitus"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Vaihda tähän: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string> </resources> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 4c599f0826ff..d118e1fcb013 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informations de profil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Avant de créer un profil limité, vous devez définir un écran de verrouillage pour protéger vos applications et vos données personnelles."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> </resources> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 8e8f09dda20c..f2dcbbaed91b 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informations de profil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Avant de créer un profil limité, vous devez définir un écran de verrouillage pour protéger vos applications et vos données personnelles."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> </resources> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index e17da89088c3..9ee490f09ae2 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Información do perfil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restrinxido, precisarás configurar un bloqueo da pantalla para protexer as túas aplicacións e datos persoais."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> </resources> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 207e07f70af5..2fced2bdb6cf 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"પ્રોફાઇલ માહિતી"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"તમે પ્રતિબંધિત પ્રોફાઇલ બનાવી શકો તે પહેલાં, તમારે તમારી ઍપ્લિકેશનો અને વ્યક્તિગત ડેટાની સુરક્ષા માટે એક લૉક સ્ક્રીન સેટ કરવાની જરૂર પડશે."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"લૉક સેટ કરો"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> પર સ્વિચ કરો"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string> + <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string> </resources> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 0c953137e33a..cb4110017241 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -514,7 +514,7 @@ <string name="shared_data_title" msgid="1017034836800864953">"शेयर किया गया डेटा"</string> <string name="shared_data_summary" msgid="5516326713822885652">"शेयर किए गए डेटा को देखें और उसमें बदलाव करें"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"इस उपयोगकर्ता के साथ किसी तरह का डेटा शेयर नहीं किया गया है."</string> - <string name="shared_data_query_failure_text" msgid="3489828881998773687">"शेयर किए गए इस डेटा को लाने में कोई गड़बड़ी हुई है. फिर से कोशिश करें."</string> + <string name="shared_data_query_failure_text" msgid="3489828881998773687">"शेयर किए गए डेटा को लाने में कोई गड़बड़ी हुई है. फिर से कोशिश करें."</string> <string name="blob_id_text" msgid="8680078988996308061">"शेयर किए गए डेटा का आईडी: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> <string name="blob_expires_text" msgid="7882727111491739331">"डेटा का ऐक्सेस <xliff:g id="DATE">%s</xliff:g> को खत्म हो जाएगा"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"शेयर किए गए डेटा को मिटाने में कोई गड़बड़ी हुई."</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफ़ाइल की जानकारी"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"इससे पहले कि आप कोई प्रतिबंधित प्रोफ़ाइल बनाएं, आपको अपने ऐप्लिकेशन और व्यक्तिगत डेटा की सुरक्षा करने के लिए एक स्क्रीन लॉक सेट करना होगा."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string> + <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string> </resources> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index d0fff2130aff..bb2f17cecab7 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profiladatok"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Mielőtt létrehozhatna egy korlátozott profilt, be kell állítania egy képernyőzárat, hogy megvédje alkalmazásait és személyes adatait."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Képernyőzár beállítása"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Váltás erre: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string> </resources> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index a884c84e4a6f..9cc883a928d5 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Պրոֆիլի տեղեկություններ"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Նախքան դուք կկարողանաք ստեղծել սահմանափակ պրոֆիլ, դուք պետք է կարգավորեք էկրանի կողպումը` ձեր ծրագրերը և անձնական տվյալները պաշտպանելու համար:"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Կարգավորել կողպումը"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Անցնել <xliff:g id="USER_NAME">%s</xliff:g> պրոֆիլին"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string> </resources> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 4022babee384..d80eba24066f 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -88,7 +88,7 @@ <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Akses Internet"</string> <string name="bluetooth_profile_pbap" msgid="7064307749579335765">"Berbagi kontak"</string> <string name="bluetooth_profile_pbap_summary" msgid="2955819694801952056">"Gunakan untuk berbagi kontak"</string> - <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Berbagi sambungan internet"</string> + <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Berbagi koneksi internet"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"SMS"</string> <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Akses SIM"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> @@ -103,7 +103,7 @@ <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Tidak tersambung kepada server transfer file"</string> <string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Terhubung ke perangkat masukan"</string> <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Terhubung ke perangkat untuk akses internet"</string> - <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Berbagi sambungan internet lokal dengan perangkat"</string> + <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Berbagi koneksi internet lokal dengan perangkat"</string> <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Digunakan untuk akses internet"</string> <string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Gunakan untuk peta"</string> <string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Gunakan untuk akses SIM"</string> @@ -169,11 +169,11 @@ <string name="tts_install_data_title" msgid="1829942496472751703">"Instal data suara"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"Instal data suara yang dibutuhkan untuk sintesis suara"</string> <string name="tts_engine_security_warning" msgid="3372432853837988146">"Mesin sintesis suara ini mungkin dapat mengumpulkan semua teks yang akan diucapkan, termasuk di antaranya data pribadi seperti sandi dan nomor kartu kredit. Berasal dari <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> aplikasi. Gunakan metode masukan ini?"</string> - <string name="tts_engine_network_required" msgid="8722087649733906851">"Bahasa ini perlu sambungan jaringan yang bekerja untuk keluaran text-to-speech."</string> + <string name="tts_engine_network_required" msgid="8722087649733906851">"Bahasa ini perlu koneksi jaringan yang bekerja untuk keluaran text-to-speech."</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"Ini adalah contoh sintesis suara"</string> <string name="tts_status_title" msgid="8190784181389278640">"Status bahasa default"</string> <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> didukung sepenuhnya"</string> - <string name="tts_status_requires_network" msgid="8327617638884678896">"<xliff:g id="LOCALE">%1$s</xliff:g> membutuhkan sambungan jaringan"</string> + <string name="tts_status_requires_network" msgid="8327617638884678896">"<xliff:g id="LOCALE">%1$s</xliff:g> membutuhkan koneksi jaringan"</string> <string name="tts_status_not_supported" msgid="2702997696245523743">"<xliff:g id="LOCALE">%1$s</xliff:g> tidak didukung"</string> <string name="tts_status_checking" msgid="8026559918948285013">"Memeriksa…"</string> <string name="tts_engine_settings_title" msgid="7849477533103566291">"Setelan untuk <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string> @@ -514,11 +514,11 @@ <string name="shared_data_title" msgid="1017034836800864953">"Data bersama"</string> <string name="shared_data_summary" msgid="5516326713822885652">"Lihat dan ubah data bersama"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Tidak ada data yang dibagikan untuk pengguna ini."</string> - <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Terjadi kesalahan saat mengambil data yang dibagikan. Coba lagi."</string> + <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Terjadi error saat mengambil data yang dibagikan. Coba lagi."</string> <string name="blob_id_text" msgid="8680078988996308061">"ID data bersama: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> <string name="blob_expires_text" msgid="7882727111491739331">"Berlaku sampai <xliff:g id="DATE">%s</xliff:g>"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"Terjadi error saat menghapus data yang dibagikan."</string> - <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Tidak ada sewa yang diperoleh dari data yang dibagikan. Apakah Anda ingin menghapusnya?"</string> + <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Tidak ada lease yang diperoleh dari data yang dibagikan ini. Apakah Anda ingin menghapusnya?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"Aplikasi yang berbagi data"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"Tidak ada deskripsi yang disediakan oleh aplikasi."</string> <string name="accessor_expires_text" msgid="4625619273236786252">"Lease akan berakhir pada <xliff:g id="DATE">%s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 437e8262f156..2e96446f974d 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Upplýsingar um snið"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Áður en þú getur búið til takmarkað snið þarftu að setja upp skjálás til að vernda forritin þín og persónuleg gögn."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Velja lás"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Skipta yfir í <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 86374f4bfe45..61f35abbe526 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"פרטי פרופיל"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"לפני שתוכל ליצור פרופיל מוגבל, תצטרך להגדיר נעילת מסך כדי להגן על האפליקציות ועל הנתונים האישיים שלך."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"הגדרת נעילה"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"מעבר אל <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string> + <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string> </resources> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index ba8418bbe736..ea9cfd8a7d51 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"プロファイル情報"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"制限付きプロファイルを作成する場合は、アプリや個人データを保護するように画面ロックを設定しておく必要があります。"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ロックを設定"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> に切り替え"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string> </resources> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index da5ff9b8a6f6..106bc18f8451 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ინფორმაცია პროფილზე"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"შეზღუდული პროფილის შექმნამდე, საკუთარი აპლიკაციებისა და პირადი მონაცემების დასაცავად, უნდა დაბლოკოთ ეკრანი."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"საკეტის დაყენება"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-ზე გადართვა"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string> + <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string> </resources> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index e74f52b02ce1..0ef83bb183cb 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Профильдік ақпарат"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Шектелген профайл жасақтауға дейін қолданбалар мен жеке деректерді қорғау үшін экран бекітпесін тағайындау қажет."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Бекітпе тағайындау"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> атты пайдаланушыға ауысу"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string> </resources> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index c51f9a69f39c..4cf8beda4098 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -518,7 +518,7 @@ <string name="blob_id_text" msgid="8680078988996308061">"លេខសម្គាល់ទិន្នន័យដែលបានចែករំលែក៖ <xliff:g id="BLOB_ID">%d</xliff:g>"</string> <string name="blob_expires_text" msgid="7882727111491739331">"ផុតកំណត់នៅថ្ងៃទី <xliff:g id="DATE">%s</xliff:g>"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"មានបញ្ហាក្នុងការលុបទិន្នន័យដែលបានចែករំលែក។"</string> - <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"មិនមានការជួលដែលបានទទួលសម្រាប់ទិន្នន័យដែលបានចែករំលែកនេះទេ។ តើអ្នកចង់លុបទិន្នន័យនេះដែរទេ?"</string> + <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"មិនមានភតិសន្យាដែលបានទទួលសម្រាប់ទិន្នន័យដែលបានចែករំលែកនេះទេ។ តើអ្នកចង់លុបទិន្នន័យនេះដែរទេ?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"កម្មវិធីដែលកំពុងចែករំលែកទិន្នន័យ"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"គ្មានការពណ៌នាដែលផ្ដល់ដោយកម្មវិធីទេ។"</string> <string name="accessor_expires_text" msgid="4625619273236786252">"ភតិសន្យាផុតកំណត់នៅថ្ងៃទី <xliff:g id="DATE">%s</xliff:g>"</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ព័ត៌មានប្រវត្តិរូប"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"មុនពេលអ្នកអាចបង្កើតប្រវត្តិរូបបានដាក់កម្រិត អ្នកត្រូវរៀបចំការចាក់សោអេក្រង់ ដើម្បីការពារកម្មវិធី និងទិន្នន័យផ្ទាល់ខ្លួនរបស់អ្នក។"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"កំណត់ការចាក់សោ"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"ប្ដូរទៅ <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូលភ្ញៀវ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"លុបភ្ញៀវ"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string> </resources> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 9e37fbc9c5fa..ed854ac3af49 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -232,8 +232,7 @@ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP ವಿಳಾಸ ಮತ್ತು ಪೋರ್ಟ್"</string> <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="8578868049289910131">"QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುವ ಮೂಲಕ ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ನಲ್ಲಿ ಸಾಧನವನ್ನು ಜೋಡಿಸಿ"</string> - <!-- no translation found for adb_wireless_no_network_msg (2365795244718494658) --> - <skip /> + <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗೆ ಸಂಪರ್ಕಿಸಿ"</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, ಡೀಬಗ್, dev"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"ದೋಷ ವರದಿಯ ಶಾರ್ಟ್ಕಟ್"</string> <string name="bugreport_in_power_summary" msgid="1885529649381831775">"ದೋಷ ವರದಿ ಮಾಡಲು ಪವರ್ ಮೆನುನಲ್ಲಿ ಬಟನ್ ತೋರಿಸು"</string> @@ -432,8 +431,7 @@ <string name="power_discharge_by" msgid="4113180890060388350">"<xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>) ಸಮಯದವರೆಗೆ ಫೋನ್ ರನ್ ಆಗಬೇಕು"</string> <string name="power_discharge_by_only" msgid="92545648425937000">"<xliff:g id="TIME">%1$s</xliff:g> ಸಮಯದವರೆಗೆ ಫೋನ್ ರನ್ ಆಗಬೇಕು"</string> <string name="power_discharge_by_only_short" msgid="5883041507426914446">"<xliff:g id="TIME">%1$s</xliff:g> ರವರೆಗೆ"</string> - <!-- no translation found for power_suggestion_battery_run_out (6332089307827787087) --> - <skip /> + <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"<xliff:g id="TIME">%1$s</xliff:g> ಗಳಲ್ಲಿ ಬ್ಯಾಟರಿ ಮುಕ್ತಾಯವಾಗಬಹುದು"</string> <string name="power_remaining_less_than_duration_only" msgid="5802195288324091585">"<xliff:g id="THRESHOLD">%1$s</xliff:g> ನಿಮಿಷಕ್ಕಿಂತ ಕಡಿಮೆ ಸಮಯ ಉಳಿದಿದೆ"</string> <string name="power_remaining_less_than_duration" msgid="1812668275239801236">"<xliff:g id="THRESHOLD">%1$s</xliff:g> ಕ್ಕಿಂತ ಕಡಿಮೆ (<xliff:g id="LEVEL">%2$s</xliff:g>) ಬಾಕಿ"</string> <string name="power_remaining_more_than_subtext" msgid="7919119719242734848">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಕ್ಕಿಂತ ಹೆಚ್ಚು (<xliff:g id="LEVEL">%2$s</xliff:g>) ಬಾಕಿ"</string> @@ -511,32 +509,21 @@ <string name="media_transfer_this_device_name" msgid="2716555073132169240">"ಫೋನ್ ಸ್ಪೀಕರ್"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ಕನೆಕ್ಟ್ ಮಾಡುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ ಸಾಧನವನ್ನು ಆಫ್ ಮಾಡಿ ಹಾಗೂ ನಂತರ ಪುನಃ ಆನ್ ಮಾಡಿ"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ವೈರ್ ಹೊಂದಿರುವ ಆಡಿಯೋ ಸಾಧನ"</string> - <!-- no translation found for help_label (3528360748637781274) --> - <skip /> - <!-- no translation found for storage_category (2287342585424631813) --> - <skip /> - <!-- no translation found for shared_data_title (1017034836800864953) --> - <skip /> - <!-- no translation found for shared_data_summary (5516326713822885652) --> - <skip /> + <string name="help_label" msgid="3528360748637781274">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string> + <string name="storage_category" msgid="2287342585424631813">"ಸಂಗ್ರಹಣೆ"</string> + <string name="shared_data_title" msgid="1017034836800864953">"ಹಂಚಿಕೊಳ್ಳಲಾದ ಡೇಟಾ"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"ಹಂಚಿಕೊಳ್ಳಲಾದ ಡೇಟಾವನ್ನು ವೀಕ್ಷಿಸಿ ಮತ್ತು ಮಾರ್ಪಡಿಸಿ"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ಈ ಬಳಕೆದಾರರಿಗೆ ಯಾವುದೇ ಹಂಚಿದ ಡೇಟಾ ಇಲ್ಲ."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"ಹಂಚಿದ ಡೇಟಾವನ್ನು ಪಡೆಯುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string> - <!-- no translation found for blob_id_text (8680078988996308061) --> - <skip /> - <!-- no translation found for blob_expires_text (7882727111491739331) --> - <skip /> + <string name="blob_id_text" msgid="8680078988996308061">"ಹಂಚಿಕೊಳ್ಳಲಾದ ಡೇಟಾ ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"<xliff:g id="DATE">%s</xliff:g> ರಂದು ಅವಧಿ ಮುಕ್ತಾಯವಾಗಲಿದೆ"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"ಹಂಚಿದ ಡೇಟಾವನ್ನು ಅಳಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ಈ ಹಂಚಿದ ಡೇಟಾವನ್ನು ಗುತ್ತಿಗೆಗೆ ಪಡೆದಿಲ್ಲ. ನೀವು ಅದನ್ನು ಅಳಿಸಲು ಬಯಸುವಿರಾ?"</string> - <!-- no translation found for accessor_info_title (8289823651512477787) --> - <skip /> - <!-- no translation found for accessor_no_description_text (7510967452505591456) --> - <skip /> - <!-- no translation found for accessor_expires_text (4625619273236786252) --> - <skip /> - <!-- no translation found for delete_blob_text (2819192607255625697) --> - <skip /> - <!-- no translation found for delete_blob_confirmation_text (7807446938920827280) --> - <skip /> + <string name="accessor_info_title" msgid="8289823651512477787">"ಹಂಚಿಕೊಂಡ ಡೇಟಾವನ್ನು ಪ್ರವೇಶಿಸುವ ಆ್ಯಪ್ಗಳು"</string> + <string name="accessor_no_description_text" msgid="7510967452505591456">"ಆ್ಯಪ್ನಿಂದ ಯಾವುದೇ ವಿವರಣೆಯನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ."</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"<xliff:g id="DATE">%s</xliff:g> ರಂದು ಗುತ್ತಿಗೆ ಅವಧಿ ಮುಗಿಯುತ್ತದೆ"</string> + <string name="delete_blob_text" msgid="2819192607255625697">"ಹಂಚಿಕೊಳ್ಳಲಾದ ಡೇಟಾವನ್ನು ಅಳಿಸಿ"</string> + <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"ಈ ಹಂಚಿಕೊಳ್ಳಲಾದ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"ಬಳಕೆದಾರರು ತಮ್ಮದೇ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವಿಷಯವನ್ನು ಹೊಂದಿದ್ದಾರೆ"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"ನಿಮ್ಮ ಖಾತೆಯಿಂದ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವಿಷಯಕ್ಕೆ ಪ್ರವೇಶವನ್ನು ನೀವು ನಿರ್ಬಂಧಿಸಬಹುದು"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"ಬಳಕೆದಾರ"</string> @@ -556,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ಪ್ರೊಫೈಲ್ ಮಾಹಿತಿ"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ನೀವು ನಿರ್ಬಂಧಿತ ಪ್ರೊಫೈಲ್ ಅನ್ನು ರಚಿಸಬಹುದಾದರ ಮೊದಲು, ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೈಯಕ್ತಿಕ ಡೇಟಾವನ್ನು ರಕ್ಷಿಸಲು ನೀವು ಪರದೆಯ ಲಾಕ್ ಹೊಂದಿಸುವ ಅಗತ್ಯವಿದೆ."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ಲಾಕ್ ಹೊಂದಿಸಿ"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> ಗೆ ಬದಲಿಸಿ"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string> </resources> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index cd69dd6779cd..6f642c3f59ee 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"프로필 정보"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"제한된 프로필을 만들기 전에 화면 잠금을 설정하여 앱과 개인 데이터를 보호해야 합니다."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"잠금 설정"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>(으)로 전환"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string> + <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string> </resources> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index c05fd66e0343..417e5115ba5a 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Профилдин чоо-жайы"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Чектелген профайл түзөөрдөн мурун, сиз өзүңүздүн колдонмолоруңузду жана жеке маалыматтарыңызды коргош үчүн, бөгөттөө көшөгөсүн орнотушуңуз керек болот."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Бөгөт коюу"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> аккаунтуна которулуу"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string> </resources> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 4591ca0d9fe1..94800ea8b3a7 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profilio informacija"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Prieš kuriant apribotą profilį reikės nustatyti ekrano užraktą, kad apsaugotumėte programas ir asmeninius duomenis."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Nustatyti užraktą"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Perjungti į <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string> </resources> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index c36b7f4487dc..d7816aa54a1a 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -544,12 +544,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profila informācija"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Lai varētu izveidot ierobežotu profilu, jums jāiestata ekrāna bloķēšana, kas aizsargās jūsu lietotni un personas datus."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Iestatīt bloķēšanu"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Pārslēgties uz: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string> </resources> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index bde3e9267173..c593bee5e1f6 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"പ്രൊഫൈൽ വിവരം"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ഒരു നിയന്ത്രിത പ്രൊഫൈൽ സൃഷ്ടിക്കുന്നതിനുമുമ്പ്, നിങ്ങളുടെ അപ്ലിക്കേഷനുകളും വ്യക്തിഗത ഡാറ്റയും പരിരക്ഷിക്കുന്നതിന് ഒരു സ്ക്രീൻ ലോക്ക് സജ്ജീകരിക്കേണ്ടതുണ്ട്."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ലോക്ക് സജ്ജീകരിക്കുക"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> എന്നതിലേക്ക് മാറുക"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string> + <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string> </resources> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 38c8b8662ee2..d3e41f727409 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Профайлын мэдээлэл"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Та хязгаарлагдсан профайл үүсгэхийн өмнө өөрийн апп-ууд болон хувийн өгөгдлийг хамгаалахын тулд дэлгэцийн түгжээг тохируулах шаардлагатай."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Түгжээг тохируулах"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> руу сэлгэх"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string> </resources> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 746ef576cff3..c222bd353890 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफाइल माहिती"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"तुम्ही एक प्रतिबंधित प्रोफाईल तयार करु शकण्यापूर्वी तुम्हाला तुमचे अॅप्स आणि वैयक्तिक डेटा संरक्षित करण्यासाठी एक स्क्रीन लॉक सेट करण्याची आवश्यकता राहील."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करा"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> वर स्विच करा"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string> + <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string> </resources> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 5e400e37194d..1dc3dc4fbdb2 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Maklumat profil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Sebelum anda boleh membuat profil yang terhad, anda perlu menyediakan kunci skrin untuk melindungi apl dan data peribadi anda."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Tetapkan kunci"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Tukar kepada <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index b71f842d1783..58213a7cef04 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profilinformasjon"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Før du kan opprette en begrenset profil, må du konfigurere skjermlåsen for å beskytte appene og de personlige dataene dine."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Angi lås"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Bytt til <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string> </resources> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 79fd4698c1ae..cc26483b791b 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -431,7 +431,7 @@ <string name="power_discharge_by" msgid="4113180890060388350">"ब्याट्री लगभग <xliff:g id="TIME">%1$s</xliff:g> सम्म टिक्नु पर्छ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_discharge_by_only" msgid="92545648425937000">"लगभग <xliff:g id="TIME">%1$s</xliff:g> सम्म टिक्नु पर्छ"</string> <string name="power_discharge_by_only_short" msgid="5883041507426914446">"<xliff:g id="TIME">%1$s</xliff:g> सम्म"</string> - <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"ब्याट्री <xliff:g id="TIME">%1$s</xliff:g> बजेभित्र सकिन सक्छ"</string> + <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"ब्याट्री <xliff:g id="TIME">%1$s</xliff:g> बजेसम्ममा सकिन सक्छ"</string> <string name="power_remaining_less_than_duration_only" msgid="5802195288324091585">"<xliff:g id="THRESHOLD">%1$s</xliff:g> भन्दा कम समय बाँकी छ"</string> <string name="power_remaining_less_than_duration" msgid="1812668275239801236">"<xliff:g id="THRESHOLD">%1$s</xliff:g> भन्दा कम समय बाँकी (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_more_than_subtext" msgid="7919119719242734848">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> भन्दा बढी समय बाँकी (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> @@ -513,16 +513,12 @@ <string name="storage_category" msgid="2287342585424631813">"भण्डारण"</string> <string name="shared_data_title" msgid="1017034836800864953">"साझा डेटा"</string> <string name="shared_data_summary" msgid="5516326713822885652">"साझा डेटा हेर्नुहोस् र परिमार्जन गर्नुहोस्"</string> - <!-- no translation found for shared_data_no_blobs_text (3108114670341737434) --> - <skip /> - <!-- no translation found for shared_data_query_failure_text (3489828881998773687) --> - <skip /> + <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"यो प्रयोगकर्तासँग कुनै पनि डेटा सेयर गरिएको छैन।"</string> + <string name="shared_data_query_failure_text" msgid="3489828881998773687">"सेयर गरिएको डेटा प्राप्त गर्ने क्रममा कुनै त्रुटि भयो। फेरि प्रयास गर्नुहोस्।"</string> <string name="blob_id_text" msgid="8680078988996308061">"साझा डेटाको ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> <string name="blob_expires_text" msgid="7882727111491739331">"<xliff:g id="DATE">%s</xliff:g> मा म्याद सकिन्छ"</string> - <!-- no translation found for shared_data_delete_failure_text (3842701391009628947) --> - <skip /> - <!-- no translation found for shared_data_no_accessors_dialog_text (8903738462570715315) --> - <skip /> + <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"सेयर गरिएको डेटा मेट्ने क्रममा त्रुटि भयो।"</string> + <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"सेयर गरिएको यो डेटाका लागि कुनै ठेक्का पट्टा लिएको छैन। तपाईं यसलाई मेट्न चाहनुहुन्छ?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"साझा डेटा प्रयोग गर्ने अनुप्रयोगहरू"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"यो अनुप्रयोगले कुनै विवरण प्रदान गरेको छैन।"</string> <string name="accessor_expires_text" msgid="4625619273236786252">"लिजको म्याद <xliff:g id="DATE">%s</xliff:g> मा सकिन्छ"</string> @@ -547,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफाइल जानकारी"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"निषेधयुक्त प्रोफाइल बनाउनु अघि तपाईँको अनुप्रयोग र व्यक्तिगत डेटा सुरक्षा गर्नाका लागि तपाईँले स्क्रिन लक सेटअप गर्नु पर्दछ ।"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"लक सेट गर्नुहोस्"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"प्रयोगकर्ता बदलेर <xliff:g id="USER_NAME">%s</xliff:g> पार्नुहोस्"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string> + <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string> </resources> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index c780d6ae633f..57fc87aa8d6f 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profielinfo"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Voordat je een beperkt profiel kunt maken, moet je een schermvergrendeling instellen om je apps en persoonsgegevens te beschermen."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Vergrendeling instellen"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Overschakelen naar <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> </resources> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 82cfbe3a6ce8..3a7f2c5acaa6 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -232,8 +232,7 @@ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP ଠିକଣା ଓ ପୋର୍ଟ"</string> <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR କୋଡ୍ ସ୍କାନ୍ କରନ୍ତୁ"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="8578868049289910131">"ଏକ QR କୋଡ୍ ସ୍କାନ୍ କରି ୱାଇ-ଫାଇରେ ଡିଭାଇସ୍ ପେୟାର୍ କରନ୍ତୁ"</string> - <!-- no translation found for adb_wireless_no_network_msg (2365795244718494658) --> - <skip /> + <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"ଦୟାକରି ଏକ ୱାଇ-ଫାଇ ନେଟୱାର୍କରେ ସଂଯୋଗ କରନ୍ତୁ"</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, ଡିବଗ୍, dev"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"ବଗ୍ ରିପୋର୍ଟ ସର୍ଟକଟ୍"</string> <string name="bugreport_in_power_summary" msgid="1885529649381831775">"ବଗ୍ ରିପୋର୍ଟ ଦେବାପାଇଁ ପାୱାର୍ ମେନୁରେ ଏକ ବଟନ୍ ଦେଖନ୍ତୁ"</string> @@ -432,8 +431,7 @@ <string name="power_discharge_by" msgid="4113180890060388350">"ବ୍ୟାଟେରୀ ପାଖାପାଖି <xliff:g id="TIME">%1$s</xliff:g> ଚାଲିବ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_discharge_by_only" msgid="92545648425937000">"ବ୍ୟାଟେରୀ <xliff:g id="TIME">%1$s</xliff:g> ପର୍ଯ୍ୟନ୍ତ ଚାଲିବ"</string> <string name="power_discharge_by_only_short" msgid="5883041507426914446">"<xliff:g id="TIME">%1$s</xliff:g> ପର୍ଯ୍ୟନ୍ତ"</string> - <!-- no translation found for power_suggestion_battery_run_out (6332089307827787087) --> - <skip /> + <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"<xliff:g id="TIME">%1$s</xliff:g> ସୁଦ୍ଧା ବ୍ୟାଟେରୀର ଚାର୍ଜ ଶେଷ ହୋଇ ଯାଇପାରେ"</string> <string name="power_remaining_less_than_duration_only" msgid="5802195288324091585">"<xliff:g id="THRESHOLD">%1$s</xliff:g>ରୁ କମ୍ ସମୟ ବଳକା ଅଛି"</string> <string name="power_remaining_less_than_duration" msgid="1812668275239801236">"<xliff:g id="THRESHOLD">%1$s</xliff:g> ରୁ କମ୍ ସମୟ ବଳକା ଅଛି (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_more_than_subtext" msgid="7919119719242734848">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g>ରୁ ଅଧିକ ସମୟ ବଳକା ଅଛି(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> @@ -511,32 +509,21 @@ <string name="media_transfer_this_device_name" msgid="2716555073132169240">"ଫୋନ୍ ସ୍ପିକର୍"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ସଂଯୋଗ କରିବାରେ ସମସ୍ୟା ହେଉଛି। ଡିଭାଇସ୍ ବନ୍ଦ କରି ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ତାରଯୁକ୍ତ ଅଡିଓ ଡିଭାଇସ୍"</string> - <!-- no translation found for help_label (3528360748637781274) --> - <skip /> - <!-- no translation found for storage_category (2287342585424631813) --> - <skip /> - <!-- no translation found for shared_data_title (1017034836800864953) --> - <skip /> - <!-- no translation found for shared_data_summary (5516326713822885652) --> - <skip /> + <string name="help_label" msgid="3528360748637781274">"ସାହାଯ୍ୟ ଓ ମତାମତ"</string> + <string name="storage_category" msgid="2287342585424631813">"ଷ୍ଟୋରେଜ୍"</string> + <string name="shared_data_title" msgid="1017034836800864953">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ଦେଖନ୍ତୁ ଏବଂ ଏହାକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ଏହି ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ କୌଣସି ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ନାହିଁ।"</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ଫେଚ୍ କରିବା ସମୟରେ ଏକ ତ୍ରୁଟି ହୋଇଥିଲା। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string> - <!-- no translation found for blob_id_text (8680078988996308061) --> - <skip /> - <!-- no translation found for blob_expires_text (7882727111491739331) --> - <skip /> + <string name="blob_id_text" msgid="8680078988996308061">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"<xliff:g id="DATE">%s</xliff:g>ରେ ମିଆଦ ଶେଷ ହେଉଛି"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ଡିଲିଟ୍ କରିବା ସମୟରେ ଏକ ତ୍ରୁଟି ହୋଇଥିଲା।"</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ସେୟାର୍ କରାଯାଇଥିବା ଏହି ଡାଟା ପାଇଁ କୌଣସି ଲିଜ୍ ପ୍ରାପ୍ତ ହୋଇନାହିଁ। ଆପଣ ଏହା ଡିଲିଟ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?"</string> - <!-- no translation found for accessor_info_title (8289823651512477787) --> - <skip /> - <!-- no translation found for accessor_no_description_text (7510967452505591456) --> - <skip /> - <!-- no translation found for accessor_expires_text (4625619273236786252) --> - <skip /> - <!-- no translation found for delete_blob_text (2819192607255625697) --> - <skip /> - <!-- no translation found for delete_blob_confirmation_text (7807446938920827280) --> - <skip /> + <string name="accessor_info_title" msgid="8289823651512477787">"ଡାଟା ସେୟାର୍ କରୁଥିବା ଆପଗୁଡ଼ିକ"</string> + <string name="accessor_no_description_text" msgid="7510967452505591456">"ଆପ୍ ଦ୍ୱାରା କୌଣସି ବର୍ଣ୍ଣନା ପ୍ରଦାନ କରାଯାଇନାହିଁ।"</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"<xliff:g id="DATE">%s</xliff:g>ରେ ଲିଜର ମିଆଦ ଶେଷ ହେଉଛି"</string> + <string name="delete_blob_text" msgid="2819192607255625697">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ଡିଲିଟ୍ କରନ୍ତୁ"</string> + <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"ଆପଣ ସେୟାର୍ କରାଯାଇଥିବା ଏହି ଡାଟା ଡିଲିଟ୍ କରିବାକୁ ଚାହୁଁଥିବା ନିଶ୍ଚିତ କି?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"ଉପଯୋଗକର୍ତ୍ତାମାନଙ୍କ ପାଖରେ ନିଜର ଆପ୍ ଓ କଣ୍ଟେଣ୍ଟ ଅଛି"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"ନିଜ ଆକାଉଣ୍ଟରୁ ଆପ୍ ତଥା କଣ୍ଟେଣ୍ଟକୁ ଆପଣ ଆକ୍ସେସ୍ ରୋକିପାରିବେ"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"ଉପଯୋଗକର୍ତ୍ତା"</string> @@ -556,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ପ୍ରୋଫାଇଲ୍ ସୂଚନା"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ପ୍ରତିବନ୍ଧିତ ପ୍ରୋଫାଇଲ୍ ତିଆରି କରିବାବେଳେ, ନିଜ ଆପ୍ ଓ ବ୍ୟକ୍ତିଗତ ତଥ୍ୟର ସୁରକ୍ଷା ପାଇଁ ଏକ ସ୍କ୍ରୀନ୍ ଲକ୍ ସେଟ୍ କରନ୍ତୁ।"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ଲକ୍ ସେଟ୍ କରନ୍ତୁ"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>କୁ ସ୍ୱିଚ୍ କରନ୍ତୁ"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string> </resources> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 52ba6045c634..980225710156 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ਪ੍ਰੋਫਾਈਲ ਜਾਣਕਾਰੀ"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ ਤੁਸੀਂ ਇੱਕ ਪ੍ਰਤਿਬੰਧਿਤ ਪ੍ਰੋਫਾਈਲ ਬਣਾ ਸਕੋ, ਤੁਹਾਨੂੰ ਆਪਣੀਆਂ ਐਪਾਂ ਅਤੇ ਨਿੱਜੀ ਡਾਟਾ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਇੱਕ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਅੱਪ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"</string> <string name="user_set_lock_button" msgid="1427128184982594856">" ਲਾਕ ਸੈੱਟ ਕਰੋ"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> \'ਤੇ ਜਾਓ"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string> </resources> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 602e1796155e..734b25757bfc 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informacje o profilu"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Zanim utworzysz profil z ograniczeniami, musisz skonfigurować ekran blokady, by chronić aplikacje i osobiste dane."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Ustaw blokadę"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Przełącz na: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string> </resources> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 90ce2a973762..586040944b9e 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -544,12 +544,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informații de profil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Înainte de a putea crea un profil cu permisiuni limitate, va trebui să configurați blocarea ecranului pentru a vă proteja aplicațiile și datele personale."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Configurați blocarea"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Comutați la <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string> </resources> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 71b231c5be6f..ab230b768f20 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -278,7 +278,7 @@ <string name="private_dns_mode_off" msgid="7065962499349997041">"Отключено"</string> <string name="private_dns_mode_opportunistic" msgid="1947864819060442354">"Автоматический режим"</string> <string name="private_dns_mode_provider" msgid="3619040641762557028">"Имя хоста поставщика персонального DNS-сервера"</string> - <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"Введите имя хоста поставщика услуг DNS"</string> + <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"Введите имя хоста поставщика DNS"</string> <string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Ошибка подключения"</string> <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Показывать параметры сертификации беспроводных мониторов"</string> <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Вести подробный журнал, показывать RSSI для каждого SSID при выборе сети"</string> @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Информация о профиле"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Чтобы создать профиль с ограниченным доступом, необходимо предварительно настроить блокировку экрана для защиты приложений и личных данных"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Включить блокировку"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Переключиться на этот аккаунт: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string> </resources> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 0e2cd8499122..73cfce1d73d7 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Informácie o profile"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Pred vytvorením obmedzeného profilu je nutné najprv nastaviť zámku obrazovky na ochranu aplikácií a osobných údajov."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Nastaviť uzamknutie"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Prepnúť na používateľa <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string> </resources> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index f3bba52594a9..8e3782c1b11c 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Podatki za profil"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Preden lahko ustvarite profil z omejitvami, morate nastaviti zaklepanje zaslona, da zaščitite aplikacije in osebne podatke."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Nastavi zaklepanje"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Preklop na račun <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> </resources> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 2cd2393ab374..6d1e80a0f341 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Inform. i profilit"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Para se të mund të krijosh një profil të kufizuar, duhet të konfigurosh një kyçje të ekranit për të mbrojtur aplikacionet dhe të dhënat e tua personale."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Cakto kyçjen"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Kalo te <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string> + <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string> </resources> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 102d9252b629..775b7d504e1e 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -544,12 +544,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Подаци о профилу"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Да бисте могли да направите ограничени профил, треба да подесите закључавање екрана да бисте заштитили апликације и личне податке."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Подеси закључавање"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Пређи на корисника <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> </resources> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 50297a7dd97d..e40ba29eadc1 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profilinfo"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Innan du skapar en begränsad profil måste du konfigurera ett skärmlås för att skydda dina appar och personliga data."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurera lås"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Byt till <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string> </resources> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index bfa120867901..815896df5512 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Maelezo ya wasifu"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Kabla uunde wasifu uliowekekwa vikwazo, utahitajika kuweka skrini iliyofungwa ili kulinda programu zako na data binafsi."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Weka ufunguo"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Badili utumie <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string> </resources> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 98bb0caf95ee..5080c31c27d7 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -418,8 +418,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"வண்ணத்திருத்தம்"</string> - <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (1284746051652993443) --> - <skip /> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"வண்ணத் திருத்தத்தைப் பயன்படுத்தி உங்கள் சாதனத்தில் வண்ணம் காண்பிக்கப்படும் விதத்தைச் சரிசெய்யலாம்"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index a36b6a3d764c..0e763ddc7207 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -232,8 +232,7 @@ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP చిరునామా & పోర్ట్"</string> <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR కోడ్ను స్కాన్ చేయండి"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="8578868049289910131">"పరికరాన్ని Wi-Fi ద్వారా పెయిర్ చేయడానికి QR కోడ్ను స్కాన్ చేయండి"</string> - <!-- no translation found for adb_wireless_no_network_msg (2365795244718494658) --> - <skip /> + <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"దయచేసి Wi-Fi నెట్వర్క్కు కనెక్ట్ చేయండి"</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, డీబగ్, dev"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"బగ్ నివేదిక షార్ట్కట్"</string> <string name="bugreport_in_power_summary" msgid="1885529649381831775">"బగ్ నివేదికను తీసుకోవడానికి పవర్ మెనూలో బటన్ను చూపు"</string> @@ -418,7 +417,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"రంగుల సరి చేసే ఫీచర్తో మీరు మీ పరికరంలో రంగులో కనిపించే పద్ధతిని సర్దుబాటు చేయగలుగుతారు"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"రంగులు సరి చేసే ఫీచర్ సాయంతో, మీ పరికరంలో రంగులు కనిపించే పద్ధతిని మీరు సర్దుబాటు చేయగలుగుతారు"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> @@ -432,8 +431,7 @@ <string name="power_discharge_by" msgid="4113180890060388350">"దాదాపు <xliff:g id="TIME">%1$s</xliff:g> వరకు ఉండాలి (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_discharge_by_only" msgid="92545648425937000">"దాదాపు <xliff:g id="TIME">%1$s</xliff:g> వరకు ఉండాలి"</string> <string name="power_discharge_by_only_short" msgid="5883041507426914446">"<xliff:g id="TIME">%1$s</xliff:g> వరకు"</string> - <!-- no translation found for power_suggestion_battery_run_out (6332089307827787087) --> - <skip /> + <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"బ్యాటరీ <xliff:g id="TIME">%1$s</xliff:g> సమయానికి ఖాళీ అవ్వచ్చు"</string> <string name="power_remaining_less_than_duration_only" msgid="5802195288324091585">"<xliff:g id="THRESHOLD">%1$s</xliff:g> కంటే తక్కువ సమయం మిగిలి ఉంది"</string> <string name="power_remaining_less_than_duration" msgid="1812668275239801236">"<xliff:g id="THRESHOLD">%1$s</xliff:g> కంటే తక్కువ సమయం మిగిలి ఉంది (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_more_than_subtext" msgid="7919119719242734848">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> కంటే ఎక్కువ సమయం మిగిలి ఉంది (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> @@ -511,32 +509,21 @@ <string name="media_transfer_this_device_name" msgid="2716555073132169240">"ఫోన్ స్పీకర్"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"కనెక్ట్ చేయడంలో సమస్య ఉంది. పరికరాన్ని ఆఫ్ చేసి, ఆపై తిరిగి ఆన్ చేయండి"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"వైర్ గల ఆడియో పరికరం"</string> - <!-- no translation found for help_label (3528360748637781274) --> - <skip /> - <!-- no translation found for storage_category (2287342585424631813) --> - <skip /> - <!-- no translation found for shared_data_title (1017034836800864953) --> - <skip /> - <!-- no translation found for shared_data_summary (5516326713822885652) --> - <skip /> + <string name="help_label" msgid="3528360748637781274">"సహాయం & అభిప్రాయం"</string> + <string name="storage_category" msgid="2287342585424631813">"నిల్వ"</string> + <string name="shared_data_title" msgid="1017034836800864953">"షేర్ చేసిన డేటా"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"షేర్ చేసిన డేటాను చూసి, సవరించండి"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ఈ యూజర్ కోసం షేర్ చేసిన డేటా ఏదీ లేదు."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"షేర్ చేసిన డేటా పొందడంలో ఎర్రర్ ఏర్పడింది. మళ్లీ ట్రై చేయండి."</string> - <!-- no translation found for blob_id_text (8680078988996308061) --> - <skip /> - <!-- no translation found for blob_expires_text (7882727111491739331) --> - <skip /> + <string name="blob_id_text" msgid="8680078988996308061">"షేర్ చేసిన డేటా ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"<xliff:g id="DATE">%s</xliff:g>న గడువు ముగుస్తుంది"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"షేర్ చేసిన డేటాను తొలగించడంలో ఎర్రర్ ఏర్పడింది."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ఈ షేర్ చేసిన డేటాకు సేకరించబడిన లీజులు ఏవీ లేవు. దీన్ని మీరు తొలగించాలనుకుంటున్నారా?"</string> - <!-- no translation found for accessor_info_title (8289823651512477787) --> - <skip /> - <!-- no translation found for accessor_no_description_text (7510967452505591456) --> - <skip /> - <!-- no translation found for accessor_expires_text (4625619273236786252) --> - <skip /> - <!-- no translation found for delete_blob_text (2819192607255625697) --> - <skip /> - <!-- no translation found for delete_blob_confirmation_text (7807446938920827280) --> - <skip /> + <string name="accessor_info_title" msgid="8289823651512477787">"యాప్ల షేరింగ్ డేటా"</string> + <string name="accessor_no_description_text" msgid="7510967452505591456">"యాప్ ద్వారా ఎలాంటి వివరణ అందించబడలేదు."</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"లీజు గడువు <xliff:g id="DATE">%s</xliff:g>తో ముగుస్తుంది"</string> + <string name="delete_blob_text" msgid="2819192607255625697">"షేర్ చేసిన డేటాను తొలగించు"</string> + <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"మీరు ఖచ్చితంగా ఈ షేర్ చేసిన డేటాను తొలగించాలనుకుంటున్నారా?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"వినియోగదారులు వారి స్వంత అనువర్తనాలను మరియు కంటెంట్ను కలిగి ఉన్నారు"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"మీరు మీ ఖాతా నుండి అనువర్తనాలకు మరియు కంటెంట్కు ప్రాప్యతను పరిమితం చేయవచ్చు"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"వినియోగదారు"</string> @@ -556,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ప్రొఫైల్ సమాచారం"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్ను సృష్టించడానికి ముందు, మీ అనువర్తనాలు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్ను సెటప్ చేయాల్సి ఉంటుంది."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"లాక్ను సెట్ చేయి"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు మార్చు"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string> + <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string> </resources> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 765c5dda24f6..b24f006ddbe6 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -513,16 +513,12 @@ <string name="storage_category" msgid="2287342585424631813">"พื้นที่เก็บข้อมูล"</string> <string name="shared_data_title" msgid="1017034836800864953">"ข้อมูลที่แชร์"</string> <string name="shared_data_summary" msgid="5516326713822885652">"ดูและแก้ไขข้อมูลที่แชร์"</string> - <!-- no translation found for shared_data_no_blobs_text (3108114670341737434) --> - <skip /> - <!-- no translation found for shared_data_query_failure_text (3489828881998773687) --> - <skip /> + <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ไม่มีข้อมูลที่แชร์สำหรับผู้ใช้รายนี้"</string> + <string name="shared_data_query_failure_text" msgid="3489828881998773687">"เกิดข้อผิดพลาดขณะดึงข้อมูลที่แชร์ ลองใหม่"</string> <string name="blob_id_text" msgid="8680078988996308061">"รหัสข้อมูลที่แชร์: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> <string name="blob_expires_text" msgid="7882727111491739331">"จะหมดอายุในวันที่ <xliff:g id="DATE">%s</xliff:g>"</string> - <!-- no translation found for shared_data_delete_failure_text (3842701391009628947) --> - <skip /> - <!-- no translation found for shared_data_no_accessors_dialog_text (8903738462570715315) --> - <skip /> + <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"เกิดข้อผิดพลาดขณะลบข้อมูลที่แชร์"</string> + <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ไม่มีสัญญาเช่าที่ได้มาสำหรับข้อมูลที่แชร์นี้ คุณต้องการลบข้อมูลนี้ไหม"</string> <string name="accessor_info_title" msgid="8289823651512477787">"แอปที่แชร์ข้อมูล"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"แอปไม่ได้ให้คำอธิบายไว้"</string> <string name="accessor_expires_text" msgid="4625619273236786252">"เวลาได้รับสิทธิ์จะสิ้นสุดในวันที่ <xliff:g id="DATE">%s</xliff:g>"</string> @@ -547,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ข้อมูลโปรไฟล์"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ก่อนที่คุณจะสามารถสร้างโปรไฟล์ที่ถูกจำกัดได้ คุณจะต้องตั้งค่าล็อกหน้าจอเพื่อปกป้องแอปและข้อมูลส่วนตัวของคุณ"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string> + <string name="guest_nickname" msgid="6332276931583337261">"ผู้เข้าร่วม"</string> </resources> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 11466198bb74..8d70718c98c3 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Info sa profile"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Bago ka makakalikha ng pinaghihigpitang profile, kakailanganin mong mag-set up ng screen lock upang protektahan ang iyong apps at personal na data."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Itakda ang lock"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Lumipat sa <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string> </resources> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 3853331c3c18..3579957fc00c 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profil bilgisi"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Kısıtlanmış bir profil oluşturabilmeniz için uygulamalarınızı ve kişisel verilerinizi korumak üzere bir ekran kilidi oluşturmanız gerekir."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Kilidi ayarla"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> hesabına geç"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string> </resources> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 2eeda185f4a4..5dae4f0a63c6 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -545,12 +545,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Інформація профілю"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Перш ніж створювати обмежений профіль, потрібно налаштувати блокування екрана, щоб захистити свої програми та особисті дані."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Налаштувати блокування"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Перейти до користувача <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string> </resources> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index f802ebc93be3..ce25f2f83af4 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -300,8 +300,8 @@ <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"اگر دستیاب ہو تو ٹیدرنگ ہارڈویئر سرعت کاری کا استعمال کریں"</string> <string name="adb_warning_title" msgid="7708653449506485728">"USB ڈیبگ کرنے کی اجازت دیں؟"</string> <string name="adb_warning_message" msgid="8145270656419669221">"USB ڈیبگ کرنا صرف ڈیولپمنٹ کے مقاصد کیلئے ہے۔ اپنے کمپیوٹر اور اپنے آلہ کے درمیان ڈیٹا کاپی کرنے کیلئے اسے استعمال کریں، بغیر اطلاع کے اپنے آلہ پر ایپس انسٹال کریں اور لاگ ڈیٹا پڑھیں۔"</string> - <string name="adbwifi_warning_title" msgid="727104571653031865">"وائرلیس ڈیبگ کرنے کی اجازت دیں؟"</string> - <string name="adbwifi_warning_message" msgid="8005936574322702388">"وائرلیس ڈیبگ کرنا صرف ڈیولپمنٹ کے مقاصد کے لیے ہے۔ اپنے کمپیوٹر اور اپنے آلہ کے درمیان ڈیٹا کاپی کرنے کے لیے اسے استعمال کریں، بغیر اطلاع کے اپنے آلہ پر ایپس انسٹال کریں اور لاگ ڈیٹا پڑھیں۔"</string> + <string name="adbwifi_warning_title" msgid="727104571653031865">"وائرلیس ڈیبگنگ کی اجازت دیں؟"</string> + <string name="adbwifi_warning_message" msgid="8005936574322702388">"وائرلیس ڈیبگنگ صرف ڈیولپمنٹ کے مقاصد کے لیے ہے۔ اپنے کمپیوٹر اور اپنے آلہ کے درمیان ڈیٹا کاپی کرنے کے لیے اسے استعمال کریں، بغیر اطلاع کے اپنے آلہ پر ایپس انسٹال کریں اور لاگ ڈیٹا پڑھیں۔"</string> <string name="adb_keys_warning_message" msgid="2968555274488101220">"اپنے ذریعہ پہلے سے اجازت یافتہ سبھی کمپیوٹرز سے USB ڈیبگ کرنے کی رسائی کو کالعدم کریں؟"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"ڈویلپمنٹ ترتیبات کی اجازت دیں؟"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"یہ ترتیبات صرف ڈویلپمنٹ استعمال کے ارادے سے ہیں۔ ان سے آپ کا آلہ اور اس پر موجود ایپلیکیشنز بریک ہو سکتی یا غلط برتاؤ کر سکتی ہیں۔"</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"پروفائل کی معلومات"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ایک محدود پروفائل بنانے سے پہلے، آپ کو اپنی ایپس اور ذاتی ڈیٹا کو محفوظ کرنے کیلئے ایک اسکرین لاک سیٹ اپ کرنا ہوگا۔"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"لاک سیٹ کریں"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> پر سوئچ کریں"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string> + <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string> </resources> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 52fe39579a9d..07f3a6e9c5ba 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -520,8 +520,8 @@ <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"Umumiy maʼlumotlarni oʻchirishda xatolik yuz berdi."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Bu umumiy maʼlumotlar yuzasidan kelgan soʻrov topilmadi. Oʻchirib tashlansinmi?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"Umumiy maʼlumotlar bor ilovalar"</string> - <string name="accessor_no_description_text" msgid="7510967452505591456">"Ilova hech qanday tavsif bermagan."</string> - <string name="accessor_expires_text" msgid="4625619273236786252">"Ruxsat eskirish sanasi: <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="accessor_no_description_text" msgid="7510967452505591456">"Ilovaga hech qanday tavsif bermagan."</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"Ruxsat tugash sanasi: <xliff:g id="DATE">%s</xliff:g>"</string> <string name="delete_blob_text" msgid="2819192607255625697">"Umumiy maʼlumotlarni oʻchirish"</string> <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Umumiy maʼlumotlarni oʻchirishni xohlaysizmi?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"Foydalanuvchilar o‘zlarining ilovalari va kontenlariga egalar"</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profil haqida axborot"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Cheklangan profil yaratish uchun, shaxsiy ilovlar va ma‘lumotlarni himoyalash maqsadida avval ekran qulfini yaratish lozim."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Qulf o‘rnatish"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Bunga almashish: <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string> </resources> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index eb22f7d726e0..10ac60df77c0 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -206,11 +206,11 @@ <string name="enable_adb" msgid="8072776357237289039">"Gỡ lỗi qua USB"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"Bật chế độ gỡ lỗi khi kết nối USB"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"Thu hồi ủy quyền gỡ lỗi USB"</string> - <string name="enable_adb_wireless" msgid="6973226350963971018">"Gỡ lỗi không dây"</string> + <string name="enable_adb_wireless" msgid="6973226350963971018">"Gỡ lỗi qua Wi-Fi"</string> <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Chế độ gỡ lỗi khi có kết nối Wi-Fi"</string> <string name="adb_wireless_error" msgid="721958772149779856">"Lỗi"</string> - <string name="adb_wireless_settings" msgid="2295017847215680229">"Gỡ lỗi không dây"</string> - <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Để xem và sử dụng các thiết bị có sẵn, hãy bật tính năng gỡ lỗi không dây"</string> + <string name="adb_wireless_settings" msgid="2295017847215680229">"Gỡ lỗi qua Wi-Fi"</string> + <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Để xem và sử dụng các thiết bị có sẵn, hãy bật tính năng gỡ lỗi qua Wi-Fi"</string> <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Ghép nối thiết bị bằng mã QR"</string> <string name="adb_pair_method_qrcode_summary" msgid="3729901496856458634">"Ghép nối các thiết bị mới bằng Trình quét mã QR"</string> <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Ghép nối thiết bị bằng mã ghép nối"</string> @@ -300,8 +300,8 @@ <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"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="7708653449506485728">"Cho phép gỡ lỗi qua USB?"</string> <string name="adb_warning_message" msgid="8145270656419669221">"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="adbwifi_warning_title" msgid="727104571653031865">"Bật tính năng gỡ lỗi không dây?"</string> - <string name="adbwifi_warning_message" msgid="8005936574322702388">"Tính năng gỡ lỗi không dây 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ị mà không thông báo và đọc dữ liệu nhật ký."</string> + <string name="adbwifi_warning_title" msgid="727104571653031865">"Bật tính năng gỡ lỗi qua Wi-Fi?"</string> + <string name="adbwifi_warning_message" msgid="8005936574322702388">"Tính năng gỡ lỗi qua Wi-Fi 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ị mà không thông báo và đọc dữ liệu nhật ký."</string> <string name="adb_keys_warning_message" msgid="2968555274488101220">"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="8251234890169074553">"Cho phép cài đặt phát triển?"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"Những cài đặt này chỉ dành cho mục đích phát triển. Chúng có thể làm cho thiết bị và ứng dụng trên thiết bị của bạn bị lỗi và hoạt động sai."</string> @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Thông tin hồ sơ"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Trước khi bạn có thể tạo tiểu sử bị hạn chế, bạn sẽ cần thiết lập một màn hình khóa để bảo vệ các ứng dụng và dữ liệu cá nhân của bạn."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Thiết lập khóa"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"Chuyển sang <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string> + <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index a4727cab98a0..c24f72dbc072 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"个人资料信息"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"您需要先设置锁定屏幕来保护您的应用和个人数据,然后才可以创建受限个人资料。"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"设置屏幕锁定方式"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"切换到<xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string> + <string name="guest_nickname" msgid="6332276931583337261">"访客"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index b3f28fac551e..a4c2336d6a02 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"個人檔案資料"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"建立限制存取的個人檔案前,您必須先設定上鎖畫面來保護您的應用程式和個人資料。"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"設定上鎖畫面"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> + <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 05d4d46a6dc8..5183df3011e5 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -543,12 +543,8 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"設定檔資訊"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"如要建立設有限制的個人資料,你必須先設定螢幕鎖定來保護你的應用程式和個人資料。"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"設定鎖定"</string> - <!-- no translation found for user_switch_to_user (6975428297154968543) --> - <skip /> - <!-- no translation found for guest_new_guest (3482026122932643557) --> - <skip /> - <!-- no translation found for guest_exit_guest (5908239569510734136) --> - <skip /> - <!-- no translation found for guest_nickname (6332276931583337261) --> - <skip /> + <string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> + <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 9112602981e5..1007d8379b8e 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -566,7 +566,7 @@ <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference --> <string name="adb_pair_method_qrcode_title">Pair device with QR code</string> <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference --> - <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string> + <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code scanner</string> <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference --> <string name="adb_pair_method_code_title">Pair device with pairing code</string> <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference --> @@ -604,7 +604,7 @@ <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title --> <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string> <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description --> - <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string> + <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR code</string> <!-- [CHAR LIMIT=NONE] Toast message when trying to enable Wi-Fi debugging and no Wi-Fi network connected --> <string name="adb_wireless_no_network_msg">Please connect to a Wi\u2011Fi network</string> <!--Adb wireless search Keywords [CHAR LIMIT=NONE]--> @@ -646,6 +646,8 @@ <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string> <!-- Setting Checkbox title whether to disable WiFi Scan Throttling. [CHAR LIMIT=40] --> <string name="wifi_scan_throttling">Wi\u2011Fi scan throttling</string> + <!-- Setting Checkbox title whether to enable WiFi enhanced mac randomization. [CHAR LIMIT=40] --> + <string name="wifi_enhanced_mac_randomization">Wi\u2011Fi\u2011enhanced MAC randomization</string> <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] --> <string name="mobile_data_always_on">Mobile data always active</string> <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] --> @@ -716,6 +718,8 @@ <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string> <!-- Setting Checkbox summary whether to disable Wifi scan throttling [CHAR LIMIT=NONE] --> <string name="wifi_scan_throttling_summary">Reduces battery drain & improves network performance</string> + <!-- Setting Checkbox title whether to enable WiFi enhanced mac randomization. [CHAR LIMIT=NONE] --> + <string name="wifi_enhanced_mac_randomization_summary">This toggle affects MAC randomization behavior for client mode only.\nWhen this mode is activated, any networks that have MAC randomization enabled may have their MAC addresses re\u2011randomized during association, depending on when the client last disconnected from the network. Re\u2011randomization does not occur if the device reconnects in 4 hours or less.</string> <!-- Label indicating network has been manually marked as metered --> <string name="wifi_metered_label">Metered</string> <!-- Label indicating network has been manually marked as unmetered --> @@ -928,11 +932,15 @@ <!-- UI debug setting: show all ANRs summary [CHAR LIMIT=100] --> <string name="show_all_anrs_summary">Display App Not Responding dialog for background apps</string> - <!-- UI debug setting: show all ANRs? [CHAR LIMIT=25] --> + <!-- UI debug setting: show missing channel toasts? [CHAR LIMIT=25] --> <string name="show_notification_channel_warnings">Show notification channel warnings</string> - <!-- UI debug setting: show all ANRs summary [CHAR LIMIT=50] --> + <!-- UI debug setting: show missing channel toasts summary [CHAR LIMIT=50] --> <string name="show_notification_channel_warnings_summary">Displays on-screen warning when an app posts a notification without a valid channel</string> + <!-- UI debug setting: enforce shortcut requirements for conversation space [CHAR LIMIT=25] --> + <string name="enforce_shortcuts_for_conversations">Enforce shortcuts for conversation notifications</string> + <!-- UI debug setting: enforce shortcut requirements for conversation space summary [CHAR LIMIT=50] --> + <string name="enforce_shortcuts_for_conversations_summary">Require notifications to be backed by a long-lived sharing shortcut in order to appear in the conversation section</string> <!-- UI debug setting: force allow apps on external storage [CHAR LIMIT=50] --> <string name="force_allow_on_external">Force allow apps on external</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java index ff40d8e00603..450bdb161933 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java @@ -202,6 +202,12 @@ public class SettingsInjector { } /** + * Gives descendants a chance to log Preference click event + */ + protected void logPreferenceClick(Intent intent) { + } + + /** * Returns the settings parsed from the attributes of the * {@link SettingInjectorService#META_DATA_NAME} tag, or null. * @@ -315,6 +321,7 @@ public class SettingsInjector { // Settings > Location. Intent settingIntent = new Intent(); settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity); + logPreferenceClick(settingIntent); // Sometimes the user may navigate back to "Settings" and launch another different // injected setting after one injected setting has been launched. // diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index fb8a0b7f63ca..3024b842c2be 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -146,6 +146,7 @@ public class LocalMediaManager implements BluetoothCallback { ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { mOnTransferBluetoothDevice = connectDevice; + device.setState(MediaDeviceState.STATE_CONNECTING); cachedDevice.connect(); return; } @@ -220,7 +221,7 @@ public class LocalMediaManager implements BluetoothCallback { */ public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) { for (MediaDevice mediaDevice : devices) { - if (mediaDevice.getId().equals(id)) { + if (TextUtils.equals(mediaDevice.getId(), id)) { return mediaDevice; } } @@ -236,7 +237,7 @@ public class LocalMediaManager implements BluetoothCallback { */ public MediaDevice getMediaDeviceById(String id) { for (MediaDevice mediaDevice : mMediaDevices) { - if (mediaDevice.getId().equals(id)) { + if (TextUtils.equals(mediaDevice.getId(), id)) { return mediaDevice; } } @@ -394,6 +395,7 @@ public class LocalMediaManager implements BluetoothCallback { dispatchDeviceListUpdate(); if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { connectDevice(mOnTransferBluetoothDevice); + mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED); mOnTransferBluetoothDevice = null; } } @@ -539,6 +541,14 @@ public class LocalMediaManager implements BluetoothCallback { @Override public void onDeviceAttributesChanged() { + if (mOnTransferBluetoothDevice != null + && !((BluetoothMediaDevice) mOnTransferBluetoothDevice).getCachedDevice() + .isBusy() + && !mOnTransferBluetoothDevice.isConnected()) { + // Failed to connect + mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_DISCONNECTED); + mOnTransferBluetoothDevice = null; + } dispatchDeviceAttributesChanged(); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index 9d7e2c821297..b1234f291b74 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -18,11 +18,15 @@ package com.android.settingslib.net; import android.content.Context; import android.net.NetworkTemplate; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.util.ArrayUtils; + +import java.util.List; + /** * Utils class for data usage */ @@ -33,26 +37,42 @@ public class DataUsageUtils { * Return mobile NetworkTemplate based on {@code subId} */ public static NetworkTemplate getMobileTemplate(Context context, int subId) { - final TelephonyManager telephonyManager = context.getSystemService( - TelephonyManager.class); - final SubscriptionManager subscriptionManager = context.getSystemService( - SubscriptionManager.class); - final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( - telephonyManager.getSubscriberId()); - - if (!subscriptionManager.isActiveSubscriptionId(subId)) { - Log.i(TAG, "Subscription is not active: " + subId); - return mobileAll; + final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + final int mobileDefaultSubId = telephonyManager.getSubscriptionId(); + + final SubscriptionManager subscriptionManager = + context.getSystemService(SubscriptionManager.class); + final List<SubscriptionInfo> subInfoList = + subscriptionManager.getAvailableSubscriptionInfoList(); + if (subInfoList == null) { + Log.i(TAG, "Subscription is not inited: " + subId); + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); } - final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId) - .getMergedImsisFromGroup(); + for (SubscriptionInfo subInfo : subInfoList) { + if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) { + return getNormalizedMobileTemplate(telephonyManager, subId); + } + } + Log.i(TAG, "Subscription is not active: " + subId); + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); + } + private static NetworkTemplate getNormalizedMobileTemplate( + TelephonyManager telephonyManager, int subId) { + final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId); + final String[] mergedSubscriberIds = telephonyManager + .createForSubscriptionId(subId).getMergedImsisFromGroup(); if (ArrayUtils.isEmpty(mergedSubscriberIds)) { Log.i(TAG, "mergedSubscriberIds is null."); - return mobileAll; + return mobileTemplate; } - return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds); + return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds); + } + + private static NetworkTemplate getMobileTemplateForSubId( + TelephonyManager telephonyManager, int subId) { + return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId)); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java index 8aa0aec28fb8..a53bc9f966d2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java @@ -102,12 +102,11 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt // Turn off divider view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE); - // Enable the icon button when this Entry is a canManageSubscription entry. + // Enable the icon button when the help string in this WifiEntry is not null. final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button); final ImageView frictionImageView = (ImageView) view.findViewById( R.id.friction_icon); - if (mWifiEntry.canManageSubscription() && !mWifiEntry.isSaved() - && !mWifiEntry.isSubscription() + if (mWifiEntry.getHelpUriString() != null && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) { final Drawable drawablehelp = getDrawable(R.drawable.ic_help); drawablehelp.setTintList( diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java index 19e38081fcad..65c7786235bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java @@ -65,5 +65,17 @@ public class WifiSavedConfigUtils { } return savedConfigs; } + + /** + * Returns the count of the saved configurations on the device, including both Wi-Fi networks + * and Passpoint profiles. + * + * @param context The application context + * @param wifiManager An instance of {@link WifiManager} + * @return count of saved Wi-Fi networks + */ + public static int getAllConfigsCount(Context context, WifiManager wifiManager) { + return getAllConfigs(context, wifiManager).size(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 1c690724b1c1..7ddd64c15876 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -218,6 +218,26 @@ public class LocalMediaManagerTest { } @Test + public void getMediaDeviceById_idIsNull_shouldReturnNull() { + final MediaDevice device1 = mock(MediaDevice.class); + final MediaDevice device2 = mock(MediaDevice.class); + mLocalMediaManager.mMediaDevices.add(device1); + mLocalMediaManager.mMediaDevices.add(device2); + + when(device1.getId()).thenReturn(null); + when(device2.getId()).thenReturn(null); + + MediaDevice device = mLocalMediaManager + .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID); + + assertThat(device).isNull(); + + device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID); + + assertThat(device).isNull(); + } + + @Test public void onDeviceAdded_addDevice() { final MediaDevice device = mock(MediaDevice.class); @@ -464,6 +484,26 @@ public class LocalMediaManagerTest { } @Test + public void onDeviceAttributesChanged_failingTransferring_shouldResetState() { + final MediaDevice currentDevice = mock(MediaDevice.class); + final MediaDevice device = mock(BluetoothMediaDevice.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + mLocalMediaManager.mMediaDevices.add(device); + mLocalMediaManager.mMediaDevices.add(currentDevice); + when(device.getId()).thenReturn(TEST_DEVICE_ID_1); + when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID); + when(((BluetoothMediaDevice) device).getCachedDevice()).thenReturn(cachedDevice); + when(cachedDevice.isConnected()).thenReturn(false); + when(cachedDevice.isBusy()).thenReturn(false); + + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.connectDevice(device); + + mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged(); + verify(device).setState(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); + } + + @Test public void onRequestFailed_checkDevicesState() { mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1); mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java index a9f31ce12b42..46e699d3bed5 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java @@ -64,7 +64,7 @@ public class WifiEntryPreferenceTest { private static final String MOCK_TITLE = "title"; private static final String MOCK_SUMMARY = "summary"; - + private static final String FAKE_URI_STRING = "fakeuri"; @Before public void setUp() { @@ -155,22 +155,23 @@ public class WifiEntryPreferenceTest { } @Test - public void canManageSubscription_shouldSetImageButtonVisible() { - when(mMockWifiEntry.canManageSubscription()).thenReturn(true); + public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() { + when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING); final WifiEntryPreference pref = new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final LayoutInflater inflater = LayoutInflater.from(mContext); final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext), false); final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); + pref.onBindViewHolder(holder); assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.VISIBLE); } @Test - public void helpButton_whenCanManageSubscription_shouldSetCorrectContentDescription() { - when(mMockWifiEntry.canManageSubscription()).thenReturn(true); + public void helpButton_whenGetHelpUriStringNotNull_shouldSetCorrectContentDescription() { + when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING); final WifiEntryPreference pref = new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final LayoutInflater inflater = LayoutInflater.from(mContext); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 86ba8bba31ee..01a2b6952f2a 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -418,6 +418,7 @@ public class SettingsBackupTest { Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS, Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT, Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, Settings.Global.SAFE_BOOT_DISALLOWED, Settings.Global.SELINUX_STATUS, Settings.Global.SELINUX_UPDATE_CONTENT_URL, diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 68bd4071de05..c2015ed41b00 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -363,6 +363,7 @@ public class BugreportProgressService extends Service { public void onError(@BugreportErrorCode int errorCode) { synchronized (mLock) { stopProgressLocked(mInfo.id); + mInfo.deleteEmptyFiles(); } Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode); return; @@ -1980,6 +1981,22 @@ public class BugreportProgressService extends Service { } /** + * Deletes empty files for a given bugreport. + */ + private void deleteEmptyFiles() { + if (bugreportFile.length() == 0) { + Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile); + bugreportFile.delete(); + } + for (File file : screenshotFiles) { + if (file.length() == 0) { + Log.i(TAG, "Deleting empty screenshot file: " + file); + file.delete(); + } + } + } + + /** * Rename all screenshots files so that they contain the new {@code name} instead of the * {@code initialName} if user has changed it. */ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 791b83277571..c6f03271f931 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -255,6 +255,9 @@ <!-- Query all packages on device on R+ --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <!-- Permission to register process observer --> + <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml new file mode 100644 index 000000000000..b16d038f68f2 --- /dev/null +++ b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml @@ -0,0 +1,22 @@ +<?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 + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:color="@color/kg_user_switcher_activated_background_color" /> + <item android:color="@android:color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/qs_user_detail_avatar_frame.xml b/packages/SystemUI/res/color/qs_user_avatar_frame.xml index 344859c27e34..0c816b4feffc 100644 --- a/packages/SystemUI/res/color/qs_user_detail_avatar_frame.xml +++ b/packages/SystemUI/res/color/qs_user_avatar_frame.xml @@ -18,5 +18,5 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_activated="true" android:color="?android:attr/colorAccent" /> - <item android:color="@android:color/transparent" /> + <item android:color="@color/qs_user_switcher_avatar_background" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/bg_avatar_selected.xml b/packages/SystemUI/res/drawable/bg_avatar_selected.xml new file mode 100644 index 000000000000..84d3416b7885 --- /dev/null +++ b/packages/SystemUI/res/drawable/bg_avatar_selected.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="100" + android:viewportHeight="100"> + + <path + android:fillColor="?android:attr/colorAccent" + android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/> + +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_add_circle.xml b/packages/SystemUI/res/drawable/ic_add_circle.xml new file mode 100644 index 000000000000..79ea70704ec9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_add_circle.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:pathData="M23.9998,10.667C16.6398,10.667 10.6665,16.6403 10.6665,24.0003C10.6665,31.3603 16.6398,37.3337 23.9998,37.3337C31.3598,37.3337 37.3332,31.3603 37.3332,24.0003C37.3332,16.6403 31.3598,10.667 23.9998,10.667ZM22.6665,17.3337V22.667H17.3332V25.3337H22.6665V30.667H25.3332V25.3337H30.6665V22.667H25.3332V17.3337H22.6665ZM13.3332,24.0003C13.3332,29.8803 18.1198,34.667 23.9998,34.667C29.8798,34.667 34.6665,29.8803 34.6665,24.0003C34.6665,18.1203 29.8798,13.3337 23.9998,13.3337C18.1198,13.3337 13.3332,18.1203 13.3332,24.0003Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml deleted file mode 100644 index 8e933445be0b..000000000000 --- a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48.0dp" - android:height="48.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/colorForeground"> - <group - android:scaleX="1.2" - android:scaleY="1.2" - android:pivotX="12.0" - android:pivotY="12.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM17.000000,13.000000l-4.000000,0.000000l0.000000,4.000000l-2.000000,0.000000l0.000000,-4.000000L7.000000,13.000000l0.000000,-2.000000l4.000000,0.000000L11.000000,7.000000l2.000000,0.000000l0.000000,4.000000l4.000000,0.000000L17.000000,13.000000z"/> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_avatar_guest_user.xml b/packages/SystemUI/res/drawable/ic_avatar_guest_user.xml new file mode 100644 index 000000000000..e3a83a29b764 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_avatar_guest_user.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:pathData="M23.9998,10.667C16.6398,10.667 10.6665,16.6403 10.6665,24.0003C10.6665,31.3603 16.6398,37.3337 23.9998,37.3337C31.3598,37.3337 37.3332,31.3603 37.3332,24.0003C37.3332,16.6403 31.3598,10.667 23.9998,10.667ZM17.4265,32.3737C17.9998,31.1737 21.4932,30.0003 23.9998,30.0003C26.5065,30.0003 30.0132,31.1737 30.5732,32.3737C28.7598,33.8137 26.4798,34.667 23.9998,34.667C21.5198,34.667 19.2398,33.8137 17.4265,32.3737ZM23.9998,27.3337C25.9465,27.3337 30.5732,28.1203 32.4798,30.4403C33.8398,28.6537 34.6665,26.427 34.6665,24.0003C34.6665,18.1203 29.8798,13.3337 23.9998,13.3337C18.1198,13.3337 13.3332,18.1203 13.3332,24.0003C13.3332,26.427 14.1598,28.6537 15.5198,30.4403C17.4265,28.1203 22.0532,27.3337 23.9998,27.3337ZM23.9998,16.0003C21.4132,16.0003 19.3332,18.0803 19.3332,20.667C19.3332,23.2537 21.4132,25.3337 23.9998,25.3337C26.5865,25.3337 28.6665,23.2537 28.6665,20.667C28.6665,18.0803 26.5865,16.0003 23.9998,16.0003ZM21.9998,20.667C21.9998,21.7737 22.8932,22.667 23.9998,22.667C25.1065,22.667 25.9998,21.7737 25.9998,20.667C25.9998,19.5603 25.1065,18.667 23.9998,18.667C22.8932,18.667 21.9998,19.5603 21.9998,20.667Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_avatar_user.xml b/packages/SystemUI/res/drawable/ic_avatar_user.xml new file mode 100644 index 000000000000..b4a4a88bf155 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_avatar_user.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:pathData="M23.9998,10.667C16.6398,10.667 10.6665,16.6403 10.6665,24.0003C10.6665,31.3603 16.6398,37.3337 23.9998,37.3337C31.3598,37.3337 37.3332,31.3603 37.3332,24.0003C37.3332,16.6403 31.3598,10.667 23.9998,10.667ZM32.4798,30.4403C30.5732,28.1203 25.9465,27.3337 23.9998,27.3337C22.0532,27.3337 17.4265,28.1203 15.5198,30.4403C14.1598,28.6537 13.3332,26.427 13.3332,24.0003C13.3332,18.1203 18.1198,13.3337 23.9998,13.3337C29.8798,13.3337 34.6665,18.1203 34.6665,24.0003C34.6665,26.427 33.8398,28.6537 32.4798,30.4403ZM19.3332,20.667C19.3332,18.0803 21.4132,16.0003 23.9998,16.0003C26.5865,16.0003 28.6665,18.0803 28.6665,20.667C28.6665,23.2537 26.5865,25.3337 23.9998,25.3337C21.4132,25.3337 19.3332,23.2537 19.3332,20.667Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml b/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml index b18c3e77ef10..021c3eee50df 100644 --- a/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml +++ b/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml @@ -20,15 +20,15 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M16,19H5V7C5.0016,6.47 5.2128,5.9623 5.5875,5.5875C5.9623,5.2128 6.47,5.0016 7,5H14C14.5299,5.0016 15.0377,5.2128 15.4125,5.5875C15.7872,5.9623 15.9984,6.47 16,7V8H18V7C18,5.9391 17.5786,4.9217 16.8284,4.1716C16.0783,3.4214 15.0609,3 14,3H7C5.9391,3 4.9217,3.4214 4.1716,4.1716C3.4214,4.9217 3,5.9391 3,7V21H18V17H16V19Z" + android:pathData="M10,3H6C5.2043,3 4.4413,3.3161 3.8787,3.8787C3.3161,4.4413 3,5.2043 3,6V21H13V6C13,5.2043 12.6839,4.4413 12.1213,3.8787C11.5587,3.3161 10.7956,3 10,3ZM11,15H10V10H11V15ZM10,8C9.4696,8 8.9609,8.2107 8.5858,8.5858C8.2107,8.9609 8,9.4696 8,10V15C8,15.5304 8.2107,16.0391 8.5858,16.4142C8.9609,16.7893 9.4696,17 10,17H11V19H5V6C5,5.7348 5.1054,5.4804 5.2929,5.2929C5.4804,5.1054 5.7348,5 6,5H10C10.2652,5 10.5196,5.1054 10.7071,5.2929C10.8946,5.4804 11,5.7348 11,6V8H10Z" android:fillColor="#FF000000" /> <path - android:pathData="M19,13C18.6049,13.378 18.1309,13.6638 17.6122,13.8367C17.0935,14.0096 16.5429,14.0653 16,14V16C16.5429,16.0653 17.0935,16.0096 17.6122,15.8367C18.1309,15.6638 18.6049,15.378 19,15C19.93,14.02 20,14 21,14V12C20,12 19.93,12.02 19,13Z" + android:pathData="M18.445,11.168C17.398,11.868 16.574,11.468 15.316,11.051L14.684,12.951C15.476,13.3014 16.3252,13.5047 17.19,13.551C18.0324,13.5503 18.8555,13.2994 19.555,12.83C20.596,12.135 21.383,12.514 22.684,12.947L23.316,11.047C22.688,10.842 20.57,9.753 18.445,11.168Z" android:fillColor="#FF000000" /> <path - android:pathData="M19,9C18.6049,9.378 18.1309,9.6638 17.6122,9.8367C17.0935,10.0096 16.5429,10.0653 16,10V12C16.5429,12.0653 17.0935,12.0096 17.6122,11.8367C18.1309,11.6638 18.6049,11.378 19,11C19.93,10.02 20,10 21,10V8C20,8 19.93,8.02 19,9Z" + android:pathData="M17.189,9.659C18.0316,9.658 18.855,9.4071 19.555,8.938C20.596,8.244 21.384,8.622 22.684,9.055L23.316,7.155C22.727,6.955 20.583,5.8489 18.445,7.2719C17.406,7.966 16.615,7.59 15.317,7.156L14.683,9.056C15.4748,9.4074 16.324,9.6117 17.189,9.659V9.659Z" android:fillColor="#FF000000" /> <path - android:pathData="M10.5,8C9.7089,8 8.9355,8.2346 8.2777,8.6741C7.6199,9.1137 7.1072,9.7383 6.8045,10.4692C6.5017,11.2001 6.4225,12.0044 6.5769,12.7803C6.7312,13.5563 7.1122,14.269 7.6716,14.8284C8.231,15.3878 8.9437,15.7688 9.7196,15.9232C10.4956,16.0775 11.2998,15.9982 12.0307,15.6955C12.7616,15.3927 13.3864,14.8801 13.8259,14.2223C14.2654,13.5645 14.5,12.7911 14.5,12C14.5,10.9391 14.0786,9.9217 13.3284,9.1716C12.5783,8.4214 11.5609,8 10.5,8ZM10.5,14C10.1044,14 9.7178,13.8827 9.3889,13.663C9.06,13.4432 8.8036,13.1308 8.6522,12.7654C8.5009,12.3999 8.4613,11.9978 8.5384,11.6098C8.6156,11.2218 8.8061,10.8655 9.0858,10.5858C9.3655,10.3061 9.7219,10.1156 10.1098,10.0385C10.4978,9.9613 10.8999,10.0008 11.2654,10.1522C11.6308,10.3036 11.9432,10.56 12.1629,10.8889C12.3827,11.2178 12.5,11.6044 12.5,12C12.5,12.5304 12.2893,13.0391 11.9142,13.4142C11.5391,13.7893 11.0304,14 10.5,14Z" + android:pathData="M18.445,15.063C17.41,15.752 16.634,15.384 15.316,14.945L14.684,16.845C15.4762,17.1948 16.3253,17.3981 17.19,17.445C18.0322,17.4438 18.8551,17.1932 19.555,16.725C20.594,16.031 21.385,16.407 22.683,16.841L23.317,14.941C22.728,14.749 20.583,13.637 18.445,15.063Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml b/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml index b18c3e77ef10..9533cfe30b75 100644 --- a/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml +++ b/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml @@ -20,15 +20,18 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M16,19H5V7C5.0016,6.47 5.2128,5.9623 5.5875,5.5875C5.9623,5.2128 6.47,5.0016 7,5H14C14.5299,5.0016 15.0377,5.2128 15.4125,5.5875C15.7872,5.9623 15.9984,6.47 16,7V8H18V7C18,5.9391 17.5786,4.9217 16.8284,4.1716C16.0783,3.4214 15.0609,3 14,3H7C5.9391,3 4.9217,3.4214 4.1716,4.1716C3.4214,4.9217 3,5.9391 3,7V21H18V17H16V19Z" + android:pathData="M10,3H6C5.2043,3 4.4413,3.3161 3.8787,3.8787C3.3161,4.4413 3,5.2043 3,6V21H13V17H10C9.4696,17 8.9609,16.7893 8.5858,16.4142C8.2107,16.0391 8,15.5304 8,15V10C8,9.4696 8.2107,8.9609 8.5858,8.5858C8.9609,8.2107 9.4696,8 10,8H13V6C13,5.2043 12.6839,4.4413 12.1213,3.8787C11.5587,3.3161 10.7956,3 10,3Z" android:fillColor="#FF000000" /> <path - android:pathData="M19,13C18.6049,13.378 18.1309,13.6638 17.6122,13.8367C17.0935,14.0096 16.5429,14.0653 16,14V16C16.5429,16.0653 17.0935,16.0096 17.6122,15.8367C18.1309,15.6638 18.6049,15.378 19,15C19.93,14.02 20,14 21,14V12C20,12 19.93,12.02 19,13Z" + android:pathData="M13,10H10V15H13V10Z" android:fillColor="#FF000000" /> <path - android:pathData="M19,9C18.6049,9.378 18.1309,9.6638 17.6122,9.8367C17.0935,10.0096 16.5429,10.0653 16,10V12C16.5429,12.0653 17.0935,12.0096 17.6122,11.8367C18.1309,11.6638 18.6049,11.378 19,11C19.93,10.02 20,10 21,10V8C20,8 19.93,8.02 19,9Z" + android:pathData="M18.445,11.168C17.398,11.868 16.574,11.468 15.316,11.051L14.684,12.951C15.476,13.3014 16.3252,13.5047 17.19,13.551C18.0324,13.5503 18.8555,13.2994 19.555,12.83C20.596,12.135 21.383,12.514 22.684,12.947L23.316,11.047C22.688,10.842 20.57,9.753 18.445,11.168Z" android:fillColor="#FF000000" /> <path - android:pathData="M10.5,8C9.7089,8 8.9355,8.2346 8.2777,8.6741C7.6199,9.1137 7.1072,9.7383 6.8045,10.4692C6.5017,11.2001 6.4225,12.0044 6.5769,12.7803C6.7312,13.5563 7.1122,14.269 7.6716,14.8284C8.231,15.3878 8.9437,15.7688 9.7196,15.9232C10.4956,16.0775 11.2998,15.9982 12.0307,15.6955C12.7616,15.3927 13.3864,14.8801 13.8259,14.2223C14.2654,13.5645 14.5,12.7911 14.5,12C14.5,10.9391 14.0786,9.9217 13.3284,9.1716C12.5783,8.4214 11.5609,8 10.5,8ZM10.5,14C10.1044,14 9.7178,13.8827 9.3889,13.663C9.06,13.4432 8.8036,13.1308 8.6522,12.7654C8.5009,12.3999 8.4613,11.9978 8.5384,11.6098C8.6156,11.2218 8.8061,10.8655 9.0858,10.5858C9.3655,10.3061 9.7219,10.1156 10.1098,10.0385C10.4978,9.9613 10.8999,10.0008 11.2654,10.1522C11.6308,10.3036 11.9432,10.56 12.1629,10.8889C12.3827,11.2178 12.5,11.6044 12.5,12C12.5,12.5304 12.2893,13.0391 11.9142,13.4142C11.5391,13.7893 11.0304,14 10.5,14Z" + android:pathData="M17.189,9.659C18.0316,9.658 18.855,9.4071 19.555,8.938C20.596,8.244 21.384,8.622 22.684,9.055L23.316,7.155C22.727,6.955 20.583,5.8489 18.445,7.2719C17.406,7.966 16.615,7.59 15.317,7.156L14.683,9.056C15.4748,9.4074 16.324,9.6117 17.189,9.659V9.659Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M18.445,15.063C17.41,15.752 16.634,15.384 15.316,14.945L14.684,16.845C15.4762,17.1948 16.3253,17.3981 17.19,17.445C18.0322,17.4438 18.8551,17.1932 19.555,16.725C20.594,16.031 21.385,16.407 22.683,16.841L23.317,14.941C22.728,14.749 20.583,13.637 18.445,15.063Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_cooking_off.xml b/packages/SystemUI/res/drawable/ic_device_cooking_off.xml new file mode 100644 index 000000000000..272a3bb79d40 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_cooking_off.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M9,16H2V18H9V21H11V18C11,17.4696 10.7893,16.9609 10.4142,16.5858C10.0391,16.2107 9.5304,16 9,16Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M15,16C14.4696,16 13.9609,16.2107 13.5858,16.5858C13.2107,16.9609 13,17.4696 13,18V21H15V18H22V16H15Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M6,15H18C18.7956,15 19.5587,14.6839 20.1213,14.1213C20.6839,13.5587 21,12.7956 21,12V8H3V12C3,12.7956 3.3161,13.5587 3.8787,14.1213C4.4413,14.6839 5.2043,15 6,15ZM5,10H19V12C19,12.2652 18.8946,12.5196 18.7071,12.7072C18.5196,12.8947 18.2652,13 18,13H6C5.7348,13 5.4804,12.8947 5.2929,12.7072C5.1054,12.5196 5,12.2652 5,12V10Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M21,5H15V4C15,3.7348 14.8946,3.4804 14.7071,3.2929C14.5196,3.1053 14.2652,3 14,3H10C9.7348,3 9.4804,3.1053 9.2929,3.2929C9.1054,3.4804 9,3.7348 9,4V5H3V7H21V5Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_cooking_on.xml b/packages/SystemUI/res/drawable/ic_device_cooking_on.xml new file mode 100644 index 000000000000..3785f8bffc81 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_cooking_on.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M9,16H2V18H9V21H11V18C11,17.4696 10.7893,16.9609 10.4142,16.5858C10.0391,16.2107 9.5304,16 9,16Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M13,18V21H15V18H22V16H15C14.4696,16 13.9609,16.2107 13.5858,16.5858C13.2107,16.9609 13,17.4696 13,18Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M3,12C3,12.7956 3.3161,13.5587 3.8787,14.1213C4.4413,14.6839 5.2043,15 6,15H18C18.7956,15 19.5587,14.6839 20.1213,14.1213C20.6839,13.5587 21,12.7956 21,12V8H3V12Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M21,5H15V4C15,3.7348 14.8946,3.4804 14.7071,3.2929C14.5196,3.1053 14.2652,3 14,3H10C9.7348,3 9.4804,3.1053 9.2929,3.2929C9.1054,3.4804 9,3.7348 9,4V5H3V7H21V5Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_display_off.xml b/packages/SystemUI/res/drawable/ic_device_display_off.xml new file mode 100644 index 000000000000..07737c959339 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_display_off.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20,3H4C3.4701,3.0016 2.9623,3.2129 2.5875,3.5877C2.2128,3.9624 2.0016,4.47 2,5V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19H8V21H16V19H20C20.5287,18.9974 21.0348,18.7854 21.4078,18.4106C21.7807,18.0359 21.99,17.5287 21.99,17L22,5C21.9984,4.47 21.7872,3.9624 21.4125,3.5877C21.0377,3.2129 20.5299,3.0016 20,3V3ZM20,17H4V5H20V17Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_display_on.xml b/packages/SystemUI/res/drawable/ic_device_display_on.xml new file mode 100644 index 000000000000..2416f6bb4b83 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_display_on.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20,3H4C3.4701,3.0016 2.9623,3.2129 2.5875,3.5877C2.2128,3.9624 2.0016,4.47 2,5V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19H8V21H16V19H20C20.5287,18.9974 21.0348,18.7854 21.4078,18.4106C21.7807,18.0359 21.99,17.5287 21.99,17L22,5C21.9984,4.47 21.7872,3.9624 21.4125,3.5877C21.0377,3.2129 20.5299,3.0016 20,3V3ZM20,17H4V5H20V17Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M19,6H5V16H19V6Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_door_off.xml b/packages/SystemUI/res/drawable/ic_device_door_off.xml new file mode 100644 index 000000000000..291f312e18a6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_door_off.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M7,14H5C4.7348,14 4.4804,14.1054 4.2929,14.293C4.1054,14.4805 4,14.7348 4,15C4,15.2652 4.1054,15.5195 4.2929,15.707C4.4804,15.8946 4.7348,16 5,16H7V21H17V3H7V14ZM15,5V19H9V16H12C12.2652,16 12.5196,15.8946 12.7071,15.707C12.8946,15.5195 13,15.2652 13,15C13,14.7348 12.8946,14.4805 12.7071,14.293C12.5196,14.1054 12.2652,14 12,14H9V5H15Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M20,3C19.7348,3 19.4804,3.1054 19.2929,3.293C19.1054,3.4805 19,3.7348 19,4V8C19,8.2652 19.1054,8.5195 19.2929,8.707C19.4804,8.8946 19.7348,9 20,9C20.2652,9 20.5196,8.8946 20.7071,8.707C20.8946,8.5195 21,8.2652 21,8V4C21,3.7348 20.8946,3.4805 20.7071,3.293C20.5196,3.1054 20.2652,3 20,3Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M12,10C12.5523,10 13,9.5523 13,9C13,8.4477 12.5523,8 12,8C11.4477,8 11,8.4477 11,9C11,9.5523 11.4477,10 12,10Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_door_on.xml b/packages/SystemUI/res/drawable/ic_device_door_on.xml new file mode 100644 index 000000000000..e6cdf11f8dea --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_door_on.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20,3C19.7348,3 19.4804,3.1054 19.2929,3.293C19.1054,3.4805 19,3.7348 19,4V8C19,8.2652 19.1054,8.5195 19.2929,8.707C19.4804,8.8946 19.7348,9 20,9C20.2652,9 20.5196,8.8946 20.7071,8.707C20.8946,8.5195 21,8.2652 21,8V4C21,3.7348 20.8946,3.4805 20.7071,3.293C20.5196,3.1054 20.2652,3 20,3Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M13,15C13,14.7348 12.8946,14.4805 12.7071,14.293C12.5196,14.1054 12.2652,14 12,14H5C4.7348,14 4.4804,14.1054 4.2929,14.293C4.1054,14.4805 4,14.7348 4,15C4,15.2652 4.1054,15.5195 4.2929,15.707C4.4804,15.8946 4.7348,16 5,16H12C12.2652,16 12.5196,15.8946 12.7071,15.707C12.8946,15.5195 13,15.2652 13,15Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M7,13H12C12.5304,13 13.0391,13.2109 13.4142,13.5859C13.7893,13.961 14,14.4696 14,15C14,15.5304 13.7893,16.039 13.4142,16.4141C13.0391,16.7891 12.5304,17 12,17H7V21H17V3H7V13ZM12,8C12.1978,8 12.3911,8.0586 12.5556,8.1685C12.72,8.2783 12.8482,8.4345 12.9239,8.6172C12.9996,8.7999 13.0194,9.0013 12.9808,9.1953C12.9422,9.3893 12.847,9.5672 12.7071,9.707C12.5673,9.8469 12.3891,9.9424 12.1951,9.981C12.0011,10.0195 11.8,9.9995 11.6173,9.9238C11.4346,9.8481 11.2784,9.7201 11.1685,9.5557C11.0586,9.3912 11,9.1978 11,9C11,8.7348 11.1054,8.4805 11.2929,8.293C11.4804,8.1054 11.7348,8 12,8Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_garage_on.xml b/packages/SystemUI/res/drawable/ic_device_garage_on.xml index 8865983f0d7d..eeb4bc153c5d 100644 --- a/packages/SystemUI/res/drawable/ic_device_garage_on.xml +++ b/packages/SystemUI/res/drawable/ic_device_garage_on.xml @@ -20,9 +20,12 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M20,9L12,3L4,9V21H6V10L12,5.5L18,10V21H20V9Z" + android:pathData="M12,3L4,9V21H7V11H17V21H20V9L12,3Z" android:fillColor="#FF000000" /> <path - android:pathData="M7,11V21H17V11H7ZM15,13V15H9V13H15ZM9,19V17H15V19H9Z" + android:pathData="M15,13H9V15H15V13Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M15,17H9V19H15V17Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml new file mode 100644 index 000000000000..0d98f9e2a6f3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M18,3L15,5.25L12,3L9,5.25L6,3L2,6V21H22V6L18,3ZM8,19H4V7L6,5.5L8,7V19ZM14,19H10V7L12,5.5L14,7V19ZM20,19H16V7L18,5.5L20,7V19Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml new file mode 100644 index 000000000000..00b6af68272a --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M2,6V21H8V6L5,3L2,6Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M9,6V21H15V6L12,3L9,6Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M19,3L16,6V21H22V6L19,3Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_pergola_on.xml b/packages/SystemUI/res/drawable/ic_device_pergola_on.xml index b7113dcd042f..cbbee8c05e1b 100644 --- a/packages/SystemUI/res/drawable/ic_device_pergola_on.xml +++ b/packages/SystemUI/res/drawable/ic_device_pergola_on.xml @@ -20,7 +20,7 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M20,2C19.7348,2 19.4804,2.1054 19.2929,2.293C19.1054,2.4805 19,2.7348 19,3V4H5V3C5,2.7348 4.8946,2.4805 4.7071,2.293C4.5196,2.1054 4.2652,2 4,2C3.7348,2 3.4804,2.1054 3.2929,2.293C3.1054,2.4805 3,2.7348 3,3V21H5V10H19V21H21V3C21,2.7348 20.8946,2.4805 20.7071,2.293C20.5196,2.1054 20.2652,2 20,2ZM5,8V6H19V8H5Z" + android:pathData="M20,2C19.7348,2 19.4804,2.1054 19.2929,2.293C19.1054,2.4805 19,2.7348 19,3V4H5V3C5,2.7348 4.8946,2.4805 4.7071,2.293C4.5196,2.1054 4.2652,2 4,2C3.7348,2 3.4804,2.1054 3.2929,2.293C3.1054,2.4805 3,2.7348 3,3V21H5V10H19V21H21V3C21,2.7348 20.8946,2.4805 20.7071,2.293C20.5196,2.1054 20.2652,2 20,2Z" android:fillColor="#FF000000" /> <path android:pathData="M8,18H11V21H13V18H16V16H8V18Z" diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml index 1ba8741e4ae2..27da211b315d 100644 --- a/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml +++ b/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M16,18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97ZM10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1711,13.8809 14.4723,14.2435 14.6805,14.6598C14.8886,15.076 14.9979,15.5346 15,16H9C9.0009,15.5344 9.1098,15.0754 9.318,14.659C9.5262,14.2426 9.8281,13.8801 10.2,13.6Z" + android:pathData="M15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12ZM14.4,17.773L14.367,17.819C14.0952,18.1807 13.744,18.4752 13.3405,18.6799C12.937,18.8846 12.4919,18.9941 12.0395,18.9998C11.5871,19.0055 11.1394,18.9073 10.7309,18.7128C10.3223,18.5184 9.9638,18.2327 9.683,17.878L9.604,17.778C9.2154,17.2664 9.0034,16.6425 9,16C9.0016,15.5346 9.1108,15.0758 9.3189,14.6595C9.5271,14.2432 9.8286,13.8805 10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1714,13.8805 14.4729,14.2432 14.6811,14.6595C14.8892,15.0758 14.9984,15.5346 15,16C14.9967,16.6403 14.7862,17.2623 14.4,17.773V17.773Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml index 1ba8741e4ae2..deabb0e45b3d 100644 --- a/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml +++ b/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M16,18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97ZM10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1711,13.8809 14.4723,14.2435 14.6805,14.6598C14.8886,15.076 14.9979,15.5346 15,16H9C9.0009,15.5344 9.1098,15.0754 9.318,14.659C9.5262,14.2426 9.8281,13.8801 10.2,13.6Z" + android:pathData="M15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V12ZM11,6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V10H11V6Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_tv_off.xml b/packages/SystemUI/res/drawable/ic_device_tv_off.xml index dd91ed831bb8..f0c9b565acd6 100644 --- a/packages/SystemUI/res/drawable/ic_device_tv_off.xml +++ b/packages/SystemUI/res/drawable/ic_device_tv_off.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4ZM20,17H4V6H20V17Z" + android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4V4ZM20,17H4V6H20V17Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_tv_on.xml b/packages/SystemUI/res/drawable/ic_device_tv_on.xml index dd91ed831bb8..ed625e9420de 100644 --- a/packages/SystemUI/res/drawable/ic_device_tv_on.xml +++ b/packages/SystemUI/res/drawable/ic_device_tv_on.xml @@ -20,6 +20,9 @@ android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4ZM20,17H4V6H20V17Z" + android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4V4ZM20,17H4V6H20V17Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M19,7H5V16H19V7Z" android:fillColor="#FF000000" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml deleted file mode 100644 index 24e063506250..000000000000 --- a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml +++ /dev/null @@ -1,9 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="#FF000000" - android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_off.xml b/packages/SystemUI/res/drawable/ic_device_unknown_off.xml new file mode 100644 index 000000000000..55820d0b5f63 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_unknown_off.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M8,6.5V11.76C8.8027,12.2963 9.4117,13.0766 9.7369,13.9856C10.0622,14.8946 10.0865,15.8841 9.8062,16.8079C9.526,17.7317 8.9561,18.541 8.1807,19.1161C7.4052,19.6912 6.4654,20.0017 5.5,20.0017C4.5346,20.0017 3.5948,19.6912 2.8193,19.1161C2.0439,18.541 1.474,17.7317 1.1938,16.8079C0.9135,15.8841 0.9378,14.8946 1.2631,13.9856C1.5883,13.0766 2.1973,12.2963 3,11.76V6.5C3,5.837 3.2634,5.2011 3.7322,4.7322C4.2011,4.2634 4.837,4 5.5,4C6.163,4 6.7989,4.2634 7.2678,4.7322C7.7366,5.2011 8,5.837 8,6.5ZM3.049,16H7.949C8.0467,15.5132 7.9978,15.0084 7.8084,14.5495C7.619,14.0905 7.2976,13.6981 6.885,13.422L5.998,12.828V6.5C5.998,6.3674 5.9453,6.2402 5.8516,6.1465C5.7578,6.0527 5.6306,6 5.498,6C5.3654,6 5.2382,6.0527 5.1445,6.1465C5.0507,6.2402 4.998,6.3674 4.998,6.5V12.828L4.111,13.422C3.7061,13.6922 3.3887,14.0746 3.1976,14.5223C3.0065,14.97 2.95,15.4637 3.035,15.943C3.0363,15.951 3.0389,15.959 3.0416,15.967C3.0453,15.978 3.049,15.989 3.049,16Z" + android:fillColor="#FF000000" + android:fillType="evenOdd"/> + <path + android:pathData="M14,16H20V18H14V16Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M17,4C15.7113,4.0006 14.457,4.416 13.4228,5.1849C12.3886,5.9538 11.6294,7.0353 11.2577,8.2692C10.8859,9.5031 10.9214,10.824 11.3587,12.0362C11.7961,13.2484 12.6121,14.2876 13.686,15H20.314C21.3879,14.2876 22.204,13.2484 22.6413,12.0362C23.0786,10.824 23.1141,9.5031 22.7423,8.2692C22.3706,7.0353 21.6114,5.9538 20.5772,5.1849C19.543,4.416 18.2887,4.0006 17,4ZM19.643,13H14.357C13.7469,12.4629 13.3149,11.7528 13.1185,10.9641C12.9221,10.1753 12.9707,9.3455 13.2577,8.5851C13.5447,7.8246 14.0566,7.1697 14.7251,6.7074C15.3937,6.2452 16.1872,5.9976 17,5.9976C17.8128,5.9976 18.6063,6.2452 19.2749,6.7074C19.9434,7.1697 20.4553,7.8246 20.7423,8.5851C21.0293,9.3455 21.0779,10.1753 20.8815,10.9641C20.6851,11.7528 20.2531,12.4629 19.643,13Z" + android:fillColor="#FF000000" + android:fillType="evenOdd"/> + <path + android:pathData="M18.0607,19.5607C17.7793,19.842 17.3978,20 17,20C16.6022,20 16.2207,19.842 15.9393,19.5607C15.658,19.2794 15.5,18.8978 15.5,18.5H18.5C18.5,18.8978 18.342,19.2794 18.0607,19.5607Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_on.xml b/packages/SystemUI/res/drawable/ic_device_unknown_on.xml new file mode 100644 index 000000000000..08d9817389d2 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_unknown_on.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M8,6.5V11.76C8.8027,12.2963 9.4117,13.0766 9.7369,13.9856C10.0622,14.8946 10.0865,15.8841 9.8062,16.8079C9.526,17.7317 8.9561,18.541 8.1807,19.1161C7.4052,19.6912 6.4654,20.0017 5.5,20.0017C4.5346,20.0017 3.5948,19.6912 2.8193,19.1161C2.0439,18.541 1.474,17.7317 1.1938,16.8079C0.9135,15.8841 0.9378,14.8946 1.2631,13.9856C1.5883,13.0766 2.1973,12.2963 3,11.76V6.5C3,5.837 3.2634,5.2011 3.7322,4.7322C4.2011,4.2634 4.837,4 5.5,4C6.163,4 6.7989,4.2634 7.2678,4.7322C7.7366,5.2011 8,5.837 8,6.5ZM3.049,16H7.949C8.0469,15.5134 7.9983,15.0087 7.8093,14.5498C7.6202,14.0909 7.2992,13.6984 6.887,13.422L6,12.828V6.5C6,6.3674 5.9473,6.2402 5.8535,6.1465C5.7598,6.0527 5.6326,6 5.5,6C5.3674,6 5.2402,6.0527 5.1465,6.1465C5.0527,6.2402 5,6.3674 5,6.5V12.828L4.111,13.422C3.7061,13.6922 3.3887,14.0746 3.1976,14.5223C3.0065,14.97 2.95,15.4637 3.035,15.943C3.0363,15.951 3.0389,15.959 3.0416,15.967C3.0453,15.978 3.049,15.989 3.049,16Z" + android:fillColor="#FF000000" + android:fillType="evenOdd"/> + <path + android:pathData="M20,16V18H14V16H20Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M17,4C15.7113,4.0006 14.457,4.416 13.4228,5.1849C12.3886,5.9538 11.6294,7.0353 11.2577,8.2692C10.8859,9.5031 10.9214,10.824 11.3587,12.0362C11.796,13.2484 12.6121,14.2876 13.686,15H20.314C21.3879,14.2876 22.204,13.2484 22.6413,12.0362C23.0786,10.824 23.1141,9.5031 22.7423,8.2692C22.3706,7.0353 21.6114,5.9538 20.5772,5.1849C19.543,4.416 18.2887,4.0006 17,4Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M18.0607,19.5607C17.7794,19.842 17.3978,20 17,20C16.6022,20 16.2206,19.842 15.9393,19.5607C15.658,19.2794 15.5,18.8978 15.5,18.5H18.5C18.5,18.8978 18.342,19.2794 18.0607,19.5607Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_water_off.xml b/packages/SystemUI/res/drawable/ic_device_water_off.xml new file mode 100644 index 000000000000..e1a7846af49a --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_water_off.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17.654,7.563L12,2L6.346,7.563C5.6036,8.2877 5.0136,9.1533 4.6108,10.1094C4.2079,11.0654 4.0002,12.0924 4,13.1299C4,15.2516 4.8429,17.2863 6.3432,18.7866C7.8434,20.2869 9.8783,21.1299 12,21.1299C14.1217,21.1299 16.1566,20.2869 17.6569,18.7866C19.1572,17.2863 20,15.2516 20,13.1299C20,12.0924 19.7925,11.0654 19.3896,10.1094C18.9867,9.1533 18.3966,8.2875 17.654,7.563ZM12,19C10.4265,19.0152 8.9113,18.4056 7.7865,17.3052C6.6617,16.2048 6.0192,14.7033 6,13.1299C5.9996,12.3577 6.1541,11.5933 6.4543,10.8818C6.7546,10.1704 7.1945,9.5262 7.748,8.9878L12,4.8061L16.252,8.9888C16.8056,9.5269 17.2456,10.171 17.5458,10.8823C17.8461,11.5936 18.0005,12.3578 18,13.1299C17.9807,14.7033 17.3383,16.2048 16.2135,17.3052C15.0887,18.4056 13.5735,19.0152 12,19Z" + android:fillColor="#FF000000" /> + <path + android:pathData="M16,12C15.7348,12 15.4804,12.1054 15.2929,12.293C15.1054,12.4805 15,12.7348 15,13C15,13.7956 14.6839,14.5585 14.1213,15.1211C13.5587,15.6837 12.7956,16 12,16C11.7348,16 11.4804,16.1054 11.2929,16.293C11.1054,16.4805 11,16.7348 11,17C11,17.2652 11.1054,17.5195 11.2929,17.707C11.4804,17.8946 11.7348,18 12,18C13.3256,17.9984 14.5964,17.471 15.5338,16.5337C16.4711,15.5964 16.9984,14.3256 17,13C17,12.7348 16.8946,12.4805 16.7071,12.293C16.5196,12.1054 16.2652,12 16,12Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_water_on.xml b/packages/SystemUI/res/drawable/ic_device_water_on.xml new file mode 100644 index 000000000000..e57e053b8d4d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_water_on.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17.654,7.563L12,2L6.346,7.563C5.6036,8.2877 5.0136,9.1533 4.6108,10.1094C4.2079,11.0654 4.0002,12.0924 4,13.1299C4.0174,15.2343 4.87,17.2458 6.3703,18.7217C7.8705,20.1975 9.8956,21.017 12,21C14.1044,21.017 16.1295,20.1975 17.6297,18.7217C19.13,17.2458 19.9826,15.2343 20,13.1299C20,12.0924 19.7925,11.0654 19.3896,10.1094C18.9867,9.1533 18.3966,8.2875 17.654,7.563ZM12,18C11.7348,18 11.4804,17.8946 11.2929,17.707C11.1054,17.5195 11,17.2652 11,17C11,16.7348 11.1054,16.4805 11.2929,16.293C11.4804,16.1054 11.7348,16 12,16C12.7956,16 13.5587,15.6837 14.1213,15.1211C14.6839,14.5585 15,13.7956 15,13C15,12.7348 15.1054,12.4805 15.2929,12.293C15.4804,12.1054 15.7348,12 16,12C16.2652,12 16.5196,12.1054 16.7071,12.293C16.8946,12.4805 17,12.7348 17,13C16.9984,14.3256 16.4711,15.5964 15.5338,16.5337C14.5964,17.471 13.3256,17.9984 12,18Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/packages/SystemUI/res/drawable/rounded_user_switcher_bg.xml b/packages/SystemUI/res/drawable/kg_user_switcher_rounded_bg.xml index e3d010ee7674..13c5fe08f43a 100644 --- a/packages/SystemUI/res/drawable/rounded_user_switcher_bg.xml +++ b/packages/SystemUI/res/drawable/kg_user_switcher_rounded_bg.xml @@ -16,17 +16,17 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:attr/colorControlHighlight"> - <item> - <shape> - <solid android:color="@color/qs_user_detail_avatar_frame" /> + <item> + <shape> + <solid android:color="@color/kg_user_switcher_rounded_background_color" /> - <padding - android:left="1dp" - android:right="1dp" - android:bottom="1dp" - android:top="1dp" /> + <padding + android:left="8dp" + android:right="8dp" + android:bottom="8dp" + android:top="8dp" /> - <corners android:radius="48dp" /> - </shape> - </item> + <corners android:radius="48dp" /> + </shape> + </item> </ripple> diff --git a/packages/SystemUI/res/drawable/qs_bg_avatar.xml b/packages/SystemUI/res/drawable/qs_bg_avatar.xml new file mode 100644 index 000000000000..41176a8efc5e --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_bg_avatar.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="100" + android:viewportHeight="100"> + + <path + android:fillColor="@color/qs_user_switcher_avatar_background" + android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/> + +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml index 1f38b1ea1da5..4413b1ef3cd3 100644 --- a/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml +++ b/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml @@ -20,24 +20,24 @@ <com.android.systemui.statusbar.policy.KeyguardUserDetailItemView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dp" - android:layout_marginEnd="8dp" + android:layout_marginEnd="32dp" android:gravity="end|center_vertical" android:clickable="true" - android:background="@drawable/rounded_user_switcher_bg" - sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" - sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated"> + android:background="@drawable/kg_user_switcher_rounded_bg" + sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"> <TextView android:id="@+id/user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="13dp" + android:layout_marginStart="25dp" + android:layout_marginEnd="12dp" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" /> <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture" - android:layout_width="@dimen/framed_avatar_size" - android:layout_height="@dimen/framed_avatar_size" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" android:contentDescription="@null" sysui:badgeDiameter="18dp" sysui:badgeMargin="1dp" /> diff --git a/packages/SystemUI/res/layout/app_ops_info.xml b/packages/SystemUI/res/layout/app_ops_info.xml index bfa252c5a14b..8342a2a4a560 100644 --- a/packages/SystemUI/res/layout/app_ops_info.xml +++ b/packages/SystemUI/res/layout/app_ops_info.xml @@ -19,8 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:focusable="true" android:id="@+id/app_ops_info" - android:clickable="true" android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml index ca085b69c35d..f5cd727a6d03 100644 --- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml +++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml @@ -17,7 +17,7 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="@dimen/pip_dismiss_gradient_height" + android:layout_height="@dimen/floating_dismiss_gradient_height" android:layout_gravity="bottom|center_horizontal"> <FrameLayout diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 5119b59424d4..db81e2348cd8 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -40,29 +40,20 @@ <TextView android:id="@+id/status" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Control.Status" android:paddingTop="@dimen/control_padding_adjustment" android:paddingStart="@dimen/control_status_padding" - android:clickable="false" + android:clickable="true" android:focusable="false" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit = "marquee_forever" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/icon" /> - - <TextView - android:id="@+id/status_extra" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Control.Status" - android:paddingTop="@dimen/control_padding_adjustment" - android:paddingStart="@dimen/control_status_padding" - android:clickable="false" - android:focusable="false" - app:layout_constraintBottom_toBottomOf="@+id/icon" - app:layout_constraintStart_toEndOf="@+id/status" /> - <TextView android:id="@+id/title" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml index 00654c832525..45540f102de1 100644 --- a/packages/SystemUI/res/layout/controls_spinner_item.xml +++ b/packages/SystemUI/res/layout/controls_spinner_item.xml @@ -30,6 +30,7 @@ android:layout_gravity="center" android:layout_width="@dimen/controls_header_app_icon_size" android:layout_height="@dimen/controls_header_app_icon_size" + android:contentDescription="@null" android:layout_marginEnd="10dp" /> <TextView diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 91beeb88d87f..b32320956e26 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -34,6 +34,7 @@ android:orientation="horizontal" android:layout_width="0dp" android:layout_weight="1" + android:minHeight="48dp" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center"> @@ -43,6 +44,7 @@ android:layout_gravity="center" android:layout_width="@dimen/controls_header_app_icon_size" android:layout_height="@dimen/controls_header_app_icon_size" + android:contentDescription="@null" android:layout_marginEnd="10dp" /> <TextView @@ -72,6 +74,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingTop="30dp" - android:layout_marginLeft="@dimen/controls_list_side_margin" - android:layout_marginRight="@dimen/controls_list_side_margin" /> + android:layout_marginLeft="@dimen/global_actions_side_margin" + android:layout_marginRight="@dimen/global_actions_side_margin" /> </merge> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml index 50aa212c94a6..72cc2ddba282 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml @@ -18,16 +18,17 @@ work around this for now with LinearLayouts. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" + android:layout_width="0dp" + android:layout_weight="1" android:layout_height="wrap_content" android:gravity="center" android:paddingTop="@dimen/global_actions_grid_item_vertical_margin" android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin" android:paddingLeft="@dimen/global_actions_grid_item_side_margin" android:paddingRight="@dimen/global_actions_grid_item_side_margin" - android:layout_marginRight="3dp" - android:layout_marginLeft="3dp" - android:background="@drawable/rounded_bg_full"> + android:layout_marginRight="@dimen/control_base_item_margin" + android:layout_marginLeft="@dimen/control_base_item_margin" + android:background="@drawable/control_background"> <LinearLayout android:layout_width="@dimen/global_actions_grid_item_width" android:layout_height="@dimen/global_actions_grid_item_height" @@ -42,7 +43,7 @@ android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" android:scaleType="centerInside" - android:tint="@color/global_actions_text" /> + android:tint="@color/control_default_foreground" /> <TextView android:id="@*android:id/message" @@ -53,7 +54,7 @@ android:singleLine="true" android:gravity="center" android:textSize="12dp" - android:textColor="@color/global_actions_text" + android:textColor="@color/control_default_foreground" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView @@ -62,7 +63,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:textColor="@color/global_actions_text" + android:textColor="@color/control_default_foreground" android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index 92ae1b95264f..59c4d011166a 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -12,17 +12,17 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:theme="@style/qs_theme" - android:gravity="top | center_horizontal" + android:gravity="top" android:clipChildren="false" android:clipToPadding="false" android:layout_marginTop="@dimen/global_actions_top_margin" > <LinearLayout android:id="@android:id/list" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/global_actions_grid_side_margin" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:layout_marginLeft="@dimen/global_actions_side_margin" + android:layout_marginRight="@dimen/global_actions_side_margin" android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" android:paddingRight="@dimen/global_actions_grid_horizontal_padding" android:paddingTop="@dimen/global_actions_grid_vertical_padding" @@ -67,9 +67,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:layout_marginRight="@dimen/global_actions_grid_horizontal_padding" - android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding" /> </LinearLayout> </com.android.systemui.globalactions.MinHeightScrollView> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml index b1e51659817e..626951c8c0b7 100644 --- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml +++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml @@ -36,8 +36,8 @@ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" /> <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture" - android:layout_width="@dimen/framed_avatar_size" - android:layout_height="@dimen/framed_avatar_size" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" android:contentDescription="@null" android:backgroundTint="@color/qs_user_detail_avatar_tint" android:backgroundTintMode="src_atop" @@ -45,5 +45,5 @@ sysui:framePadding="2.5dp" sysui:badgeDiameter="18dp" sysui:badgeMargin="1dp" - sysui:frameColor="@color/qs_user_detail_avatar_frame" /> + sysui:frameColor="@color/kg_user_switcher_rounded_background_color" /> </com.android.systemui.statusbar.policy.KeyguardUserDetailItemView> diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 87cb5c7f746c..9dc502efab43 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -20,7 +20,7 @@ android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" + android:focusable="true" android:clipChildren="false" android:clipToPadding="true" android:orientation="vertical" diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml index dc94697f32fa..5399f57c322f 100644 --- a/packages/SystemUI/res/layout/notification_guts.xml +++ b/packages/SystemUI/res/layout/notification_guts.xml @@ -19,8 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:focusable="true" android:id="@+id/notification_guts" android:visibility="gone" - android:clickable="true" android:gravity="top|start" android:theme="@*android:style/Theme.DeviceDefault.Light"/> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 73b711d275f3..5b363820e4e2 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -20,7 +20,7 @@ android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" + android:focusable="true" android:clipChildren="false" android:clipToPadding="true" android:orientation="vertical" diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml deleted file mode 100644 index 2cc4b220fe2b..000000000000 --- a/packages/SystemUI/res/layout/pip_dismiss_view.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?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. ---> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/pip_dismiss_gradient_height" - android:alpha="0"> - - <TextView - android:id="@+id/pip_dismiss_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|center_horizontal" - android:text="@string/pip_phone_dismiss_hint" - android:textColor="#FFFFFFFF" - android:textSize="14sp" - android:shadowColor="@android:color/black" - android:shadowDx="-2" - android:shadowDy="2" - android:shadowRadius="0.01" /> - -</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml index fc3bf941b27a..e5ac5f89cd25 100644 --- a/packages/SystemUI/res/layout/qs_media_panel.xml +++ b/packages/SystemUI/res/layout/qs_media_panel.xml @@ -23,7 +23,8 @@ android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal|fill_vertical" - android:padding="16dp" + android:paddingTop="@dimen/qs_media_panel_outer_padding" + android:paddingBottom="@dimen/qs_media_panel_outer_padding" android:background="@drawable/qs_media_background" > @@ -42,7 +43,9 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="@dimen/qs_media_panel_outer_padding" + android:paddingStart="@dimen/qs_media_panel_outer_padding" + android:paddingEnd="16dp" > <ImageView @@ -139,6 +142,7 @@ <!-- Seek Bar --> <SeekBar android:id="@+id/media_progress_bar" + style="@android:style/Widget.ProgressBar.Horizontal" android:clickable="true" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -154,6 +158,9 @@ android:id="@+id/notification_media_progress_time" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingStart="@dimen/qs_media_panel_outer_padding" + android:paddingEnd="@dimen/qs_media_panel_outer_padding" + android:layout_marginBottom="10dp" android:layout_gravity="center" > <!-- width is set to "match_parent" to avoid extra layout calls --> @@ -184,6 +191,8 @@ android:layoutDirection="ltr" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingStart="@dimen/qs_media_panel_outer_padding" + android:paddingEnd="@dimen/qs_media_panel_outer_padding" android:gravity="center" > <ImageButton diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index 5c03e3099522..0608685c3440 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -30,21 +30,20 @@ android:clipToPadding="false" android:focusable="true" android:background="@drawable/ripple_drawable" - systemui:regularTextAppearance="@style/TextAppearance.QS.UserSwitcher" - systemui:activatedTextAppearance="@style/TextAppearance.QS.UserSwitcher.Activated"> + systemui:regularTextAppearance="@style/TextAppearance.QS.UserSwitcher"> <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture" - android:layout_width="@dimen/framed_avatar_size" - android:layout_height="@dimen/framed_avatar_size" + android:layout_width="@dimen/qs_framed_avatar_size" + android:layout_height="@dimen/qs_framed_avatar_size" android:layout_marginBottom="7dp" android:backgroundTint="@color/qs_user_detail_avatar_tint" android:backgroundTintMode="src_atop" - systemui:frameWidth="2dp" - systemui:framePadding="2.5dp" + systemui:frameWidth="6dp" systemui:badgeDiameter="18dp" systemui:badgeMargin="1dp" - systemui:frameColor="@color/qs_user_detail_avatar_frame"/> + systemui:framePadding="-1dp" + systemui:frameColor="@color/qs_user_avatar_frame"/> <LinearLayout android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 63bbe62a6520..da5819c50a7e 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -31,4 +31,7 @@ <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen --> <integer name="navigation_bar_deadzone_orientation">1</integer> + + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">4</integer> </resources> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 9b0fe465deee..93aa2701ad07 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -86,4 +86,15 @@ <color name="GM2_red_500">#E25142</color> <color name="GM2_yellow_500">#F5C518</color> + <!-- Icon color for user avatars in keyguard user switcher --> + <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color> + <!-- Icon color for selected user avatars in keyguard user switcher --> + <color name="kg_user_switcher_selected_avatar_icon_color">#202124</color> + <!-- Icon color for user avatars in quick settings user switcher --> + <color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color> + <!-- Icon color for selected user avatars in quick settings user switcher --> + <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color> + <!-- Color of background circle of user avatars in quick settings user switcher --> + <color name="qs_user_switcher_avatar_background">#3C4043</color> + </resources> diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml index 2774554ec642..c110113e91f4 100644 --- a/packages/SystemUI/res/values-sw320dp/dimens.xml +++ b/packages/SystemUI/res/values-sw320dp/dimens.xml @@ -30,5 +30,7 @@ <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Home Controls --> + <dimen name="global_actions_side_margin">10dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml index dfd97435a288..fc510bf03efd 100644 --- a/packages/SystemUI/res/values-sw360dp/dimens.xml +++ b/packages/SystemUI/res/values-sw360dp/dimens.xml @@ -26,5 +26,7 @@ navigation_extra_key_width --> <dimen name="navigation_side_padding">40dip</dimen> + <!-- Home Controls --> + <dimen name="global_actions_side_margin">12dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml index be1d9528db1d..4c9d02e30bfa 100644 --- a/packages/SystemUI/res/values-sw392dp/dimens.xml +++ b/packages/SystemUI/res/values-sw392dp/dimens.xml @@ -30,5 +30,7 @@ <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Home Controls --> + <dimen name="global_actions_side_margin">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 89341838f3e3..8f73d231c732 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -45,6 +45,11 @@ <!-- Height of the status bar header bar when on Keyguard --> <dimen name="status_bar_header_height_keyguard">60dp</dimen> + <!-- Size of user icon + frame in the qs user picker (incl. frame) --> + <dimen name="qs_framed_avatar_size">60dp</dimen> + <!-- Size of user icon + frame in the keyguard user picker (incl. frame) --> + <dimen name="kg_framed_avatar_size">48dp</dimen> + <!-- The width of user avatar when on Keyguard --> <dimen name="multi_user_switch_width_keyguard">48dp</dimen> @@ -88,4 +93,7 @@ <!-- Keyboard shortcuts helper --> <dimen name="ksh_layout_width">488dp</dimen> + + <!-- Text size for user name in user switcher --> + <dimen name="kg_user_switcher_text_size">18sp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml index b375364fdab9..02bd60210e81 100644 --- a/packages/SystemUI/res/values-sw600dp/styles.xml +++ b/packages/SystemUI/res/values-sw600dp/styles.xml @@ -30,20 +30,10 @@ <item name="android:textColor">?attr/wallpaperTextColor</item> </style> - <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated"> - <item name="android:fontWeight">700</item> - <item name="android:textStyle">bold</item> - <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> - </style> - <style name="TextAppearance.QS.UserSwitcher"> - <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item> + <item name="android:textSize">@dimen/kg_user_switcher_text_size</item> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> </style> - <style name="TextAppearance.QS.UserSwitcher.Activated"> - <item name="android:fontWeight">700</item> - <item name="android:textStyle">bold</item> - </style> </resources> diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml index 95716c834483..3343a84e7a9c 100644 --- a/packages/SystemUI/res/values/arrays_tv.xml +++ b/packages/SystemUI/res/values/arrays_tv.xml @@ -35,7 +35,5 @@ </string-array> <string-array name="audio_recording_disclosure_exempt_apps" translatable="false"> - <item>com.google.android.katniss</item> - <item>com.google.android.apps.mediashell</item> </string-array> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 74bbee681ccc..4482cdac3327 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -50,6 +50,10 @@ <!-- The color of the text in the Global Actions menu --> <color name="global_actions_alert_text">@color/GM2_red_700</color> + <!-- The color of the background of the emergency button when home controls are visible --> + <color name="global_actions_emergency_background">@color/GM2_red_400</color> + <color name="global_actions_emergency_text">@color/GM2_grey_100</color> + <!-- Tint color for the content on the notification overflow card. --> <color name="keyguard_overflow_content_color">#ff686868</color> @@ -58,6 +62,19 @@ <!-- Shadows under the clock, date and other keyguard text fields --> <color name="keyguard_shadow_color">#B2000000</color> + <!-- Color for rounded background for activated user in keyguard user switcher --> + <color name="kg_user_switcher_activated_background_color">#26000000</color> + <!-- Icon color for user avatars in keyguard user switcher --> + <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color> + <!-- Icon color for selected user avatars in keyguard user switcher --> + <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color> + <!-- Icon color for user avatars in user switcher quick settings --> + <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> + <!-- Icon color for selected user avatars in user switcher quick settings --> + <color name="qs_user_switcher_selected_avatar_icon_color">@android:color/background_light</color> + <!-- Color of background circle of user avatars in quick settings --> + <color name="qs_user_switcher_avatar_background">#DADCE0</color> + <!-- The color of the legacy notification background --> <color name="notification_legacy_background_color">#ff1a1a1a</color> @@ -198,6 +215,7 @@ <color name="GM2_red_50">#FCE8E6</color> <color name="GM2_red_200">#F6AEA9</color> <color name="GM2_red_300">#F28B82</color> + <color name="GM2_red_400">#EE675C</color> <color name="GM2_red_500">#B71C1C</color> <color name="GM2_red_700">#C5221F</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 922acff7f6f2..f549a3253319 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -533,4 +533,12 @@ This config value should contain the package name of that preferred application. --> <string translatable="false" name="config_controlsPreferredPackage"></string> + + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">2</integer> + <!-- If the dp width of the available space is <= this value, potentially adjust the number + of columns--> + <integer name="controls_max_columns_adjust_below_width_dp">320</integer> + <!-- If the config font scale is >= this value, potentially adjust the number of columns--> + <item name="controls_max_columns_adjust_above_font_scale" translatable="false" format="float" type="dimen">1.25</item> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5b213edd5f0f..6a8a4b9ac2fd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -752,8 +752,10 @@ quick settings header --> <dimen name="max_avatar_size">48dp</dimen> - <!-- Size of user icon + frame in the qs/keyguard user picker (incl. frame) --> - <dimen name="framed_avatar_size">54dp</dimen> + <!-- Size of user icon + frame in the qs user picker (incl. frame) --> + <dimen name="qs_framed_avatar_size">54dp</dimen> + <!-- Size of user icon + frame in the keyguard user picker (incl. frame) --> + <dimen name="kg_framed_avatar_size">54dp</dimen> <!-- Margin on the left side of the carrier text on Keyguard --> <dimen name="keyguard_carrier_text_margin">16dp</dimen> @@ -953,7 +955,7 @@ <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen> <!-- The height of the gradient indicating the dismiss edge when moving a PIP. --> - <dimen name="pip_dismiss_gradient_height">176dp</dimen> + <dimen name="floating_dismiss_gradient_height">176dp</dimen> <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. --> <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen> @@ -1006,6 +1008,9 @@ <dimen name="global_actions_grid_container_shadow_offset">20dp</dimen> <dimen name="global_actions_grid_container_negative_shadow_offset">-20dp</dimen> + <!-- Margins at the left and right of the power menu and home controls widgets. --> + <dimen name="global_actions_side_margin">16dp</dimen> + <!-- The maximum offset in either direction that elements are moved horizontally to prevent burn-in on AOD. --> <dimen name="burn_in_prevention_offset_x">8dp</dimen> @@ -1209,6 +1214,7 @@ <!-- Size of media cards in the QSPanel carousel --> <dimen name="qs_media_width">350dp</dimen> <dimen name="qs_media_padding">8dp</dimen> + <dimen name="qs_media_panel_outer_padding">16dp</dimen> <dimen name="qs_media_corner_radius">10dp</dimen> <dimen name="qs_media_album_size">72dp</dimen> <dimen name="qs_seamless_icon_size">20dp</dimen> @@ -1229,7 +1235,6 @@ <dimen name="controls_header_side_margin">4dp</dimen> <dimen name="controls_header_menu_size">48dp</dimen> <dimen name="controls_header_app_icon_size">40dp</dimen> - <dimen name="controls_list_side_margin">16dp</dimen> <dimen name="controls_top_margin">44dp</dimen> <dimen name="control_header_text_size">22sp</dimen> <dimen name="control_text_size">14sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d17b81d46090..566d143208fc 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2647,15 +2647,15 @@ <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] --> <string name="magnification_controls_title">Magnification Window Controls</string> - <!-- Quick Controls strings --> - <!-- Quick Controls empty state, title [CHAR LIMIT=30] --> - <string name="quick_controls_title">Quick controls</string> - <!-- Quick Controls empty state, subtitle [CHAR LIMIT=100] --> + <!-- Device Controls strings --> + <!-- Device Controls empty state, title [CHAR LIMIT=30] --> + <string name="quick_controls_title">Device controls</string> + <!-- Device Controls empty state, subtitle [CHAR LIMIT=100] --> <string name="quick_controls_subtitle">Add controls for your connected devices</string> - <!-- Quick Controls setup, title [CHAR LIMIT=50] --> - <string name="quick_controls_setup_title">Set up quick controls</string> - <!-- Quick Controls setup, subtitle [CHAR LIMIT=100] --> + <!-- Device Controls setup, title [CHAR LIMIT=50] --> + <string name="quick_controls_setup_title">Set up device controls</string> + <!-- Device Controls setup, subtitle [CHAR LIMIT=100] --> <string name="quick_controls_setup_subtitle">Hold the Power button to access your controls</string> <!-- Controls management providers screen title [CHAR LIMIT=60]--> @@ -2679,7 +2679,7 @@ <string name="controls_favorite_other_zone_header">Other</string> <!-- Controls dialog title [CHAR LIMIT=40] --> - <string name="controls_dialog_title">Add to quick controls</string> + <string name="controls_dialog_title">Add to device controls</string> <!-- Controls dialog add to favorites [CHAR LIMIT=40] --> <string name="controls_dialog_ok">Add to favorites</string> <!-- Controls dialog message [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1598465d6df0..3e02b30c376b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -249,6 +249,7 @@ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> <style name="TextAppearance.AuthCredential"> + <item name="android:accessibilityLiveRegion">polite</item> <item name="android:gravity">center_horizontal</item> <item name="android:textAlignment">gravity</item> <item name="android:layout_gravity">top</item> @@ -510,7 +511,7 @@ </style> <style name="TextAppearance.NotificationInfo"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">@color/notification_primary_text_color</item> </style> @@ -520,7 +521,6 @@ </style> <style name="TextAppearance.NotificationInfo.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">@color/notification_primary_text_color</item> <item name="android:textStyle">bold</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 3bda3c8df699..806678f23bb3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -261,6 +261,11 @@ public class ActivityManagerWrapper { animationHandler.onAnimationCanceled( taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) { + animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app)); + } }; } ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index bbb83c73446c..76513c6ff3d5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -109,4 +109,16 @@ public class RecentsAnimationControllerCompat { Log.e(TAG, "Failed to set overview reached state", e); } } + + /** + * @see IRecentsAnimationController#removeTask + */ + public boolean removeTask(int taskId) { + try { + return mAnimationController.removeTask(taskId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove remote animation target", e); + return false; + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index 2c99c5c84015..c4cd192212a0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -32,4 +32,10 @@ public interface RecentsAnimationListener { * Called when the animation into Recents was canceled. This call is made on the binder thread. */ void onAnimationCanceled(ThumbnailData thumbnailData); + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(RemoteAnimationTargetCompat app); } diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 7eb5a8f2b06c..e99245fa438f 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -34,6 +34,8 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import java.util.NoSuchElementException; + /** * Encapsulates all logic for secondary lockscreen state management. */ @@ -79,7 +81,9 @@ public class AdminSecondaryLockScreenController { private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() { @Override public void onDismiss() { - dismiss(UserHandle.getCallingUserId()); + mHandler.post(() -> { + dismiss(UserHandle.getCallingUserId()); + }); } @Override @@ -91,7 +95,9 @@ public class AdminSecondaryLockScreenController { if (surfacePackage != null) { mView.setChildSurfacePackage(surfacePackage); } else { - dismiss(KeyguardUpdateMonitor.getCurrentUser()); + mHandler.post(() -> { + dismiss(KeyguardUpdateMonitor.getCurrentUser()); + }); } } }; @@ -122,6 +128,7 @@ public class AdminSecondaryLockScreenController { // If the remote content is not readied within the timeout period, // move on without the secondary lockscreen. dismiss(userId); + Log.w(TAG, "Timed out waiting for secondary lockscreen content."); }, REMOTE_CONTENT_READY_TIMEOUT_MILLIS); } @@ -150,8 +157,12 @@ public class AdminSecondaryLockScreenController { * Displays the Admin security Surface view. */ public void show(Intent serviceIntent) { - mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); - mParent.addView(mView); + if (mClient == null) { + mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); + } + if (!mView.isAttachedToWindow()) { + mParent.addView(mView); + } } /** @@ -162,7 +173,11 @@ public class AdminSecondaryLockScreenController { mParent.removeView(mView); } if (mClient != null) { - mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0); + try { + mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "IKeyguardClient death recipient already released"); + } mContext.unbindService(mConnection); mClient = null; } @@ -185,10 +200,12 @@ public class AdminSecondaryLockScreenController { private void dismiss(int userId) { mHandler.removeCallbacksAndMessages(null); - if (mView != null && mView.isAttachedToWindow() - && userId == KeyguardUpdateMonitor.getCurrentUser()) { + if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) { hide(); - mKeyguardCallback.dismiss(true, userId); + if (mKeyguardCallback != null) { + mKeyguardCallback.dismiss(/* securityVerified= */ true, userId, + /* bypassSecondaryLockScreen= */true); + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index caee8ccb6970..88f4176f5eac 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -34,6 +34,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; +import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -50,6 +51,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout private boolean mDismissing; protected boolean mResumed; private CountDownTimer mCountdownTimer = null; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. @@ -61,6 +63,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { super(context, attrs); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } @Override @@ -151,6 +154,8 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); } + + mKeyguardUpdateMonitor.setCredentialAttempted(); mPendingLockCheck = LockPatternChecker.checkCredential( mLockPatternUtils, password, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index d5a08dda9853..aa2fe3c7f8fc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -85,7 +85,8 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { // the user proved presence via some other way to the trust agent. Log.i(TAG, "TrustAgent dismissed Keyguard."); } - dismiss(false /* authenticated */, userId); + dismiss(false /* authenticated */, userId, + /* bypassSecondaryLockScreen */ false); } else { mViewMediatorCallback.playTrustedSound(); } @@ -190,7 +191,7 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { * @return True if the keyguard is done. */ public boolean dismiss(int targetUserId) { - return dismiss(false, targetUserId); + return dismiss(false, targetUserId, false); } public boolean handleBackKey() { @@ -206,8 +207,10 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { } @Override - public boolean dismiss(boolean authenticated, int targetUserId) { - return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId); + public boolean dismiss(boolean authenticated, int targetUserId, + boolean bypassSecondaryLockScreen) { + return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId, + bypassSecondaryLockScreen); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java index d1544346a25a..af5196f92bcb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; import android.util.Log; import android.view.View; import android.widget.ImageButton; @@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.media.MediaControllerFactory; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.stack.MediaHeaderView; @@ -71,10 +74,11 @@ public class KeyguardMediaPlayer { private KeyguardMediaObserver mObserver; @Inject - public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) { + public KeyguardMediaPlayer(Context context, MediaControllerFactory factory, + @Background Executor backgroundExecutor) { mContext = context; mBackgroundExecutor = backgroundExecutor; - mViewModel = new KeyguardMediaViewModel(context); + mViewModel = new KeyguardMediaViewModel(context, factory); } /** Binds media controls to a view hierarchy. */ @@ -139,14 +143,16 @@ public class KeyguardMediaPlayer { private static final class KeyguardMediaViewModel { private final Context mContext; + private final MediaControllerFactory mMediaControllerFactory; private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>(); private final Object mActionsLock = new Object(); private List<PendingIntent> mActions; private float mAlbumArtRadius; private int mAlbumArtSize; - KeyguardMediaViewModel(Context context) { + KeyguardMediaViewModel(Context context, MediaControllerFactory factory) { mContext = context; + mMediaControllerFactory = factory; loadDimens(); } @@ -162,6 +168,17 @@ public class KeyguardMediaPlayer { public void updateControls(NotificationEntry entry, Icon appIcon, MediaMetadata mediaMetadata) { + // Check the playback state of the media controller. If it is null, then the session was + // probably destroyed. Don't update in this case. + final MediaSession.Token token = entry.getSbn().getNotification().extras + .getParcelable(Notification.EXTRA_MEDIA_SESSION); + final MediaController controller = token != null + ? mMediaControllerFactory.create(token) : null; + if (controller != null && controller.getPlaybackState() == null) { + clearControls(); + return; + } + // Foreground and Background colors computed from album art Notification notif = entry.getSbn().getNotification(); int fgColor = notif.color; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 48c6bd114d4a..ad92f8f623e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -282,6 +282,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void onPatternDetected(final List<LockPatternView.Cell> pattern) { + mKeyguardUpdateMonitor.setCredentialAttempted(); mLockPatternView.disableInput(); if (mPendingLockCheck != null) { mPendingLockCheck.cancel(false); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java index 49dcfffb0d72..e38472745234 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -25,6 +25,15 @@ public interface KeyguardSecurityCallback { void dismiss(boolean securityVerified, int targetUserId); /** + * Dismiss the given security screen. + * @param securityVerified true if the user correctly entered credentials for the given screen. + * @param targetUserId a user that needs to be the foreground user at the dismissal completion. + * @param bypassSecondaryLockScreen true if the user can bypass the secondary lock screen, + * if any, during this dismissal. + */ + void dismiss(boolean securityVerified, int targetUserId, boolean bypassSecondaryLockScreen); + + /** * Manually report user activity to keep the device awake. */ void userActivity(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index ba8a1a945a77..1e1ce4e6d159 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -115,7 +115,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe // Used to notify the container when something interesting happens. public interface SecurityCallback { - public boolean dismiss(boolean authenticated, int targetUserId); + public boolean dismiss(boolean authenticated, int targetUserId, + boolean bypassSecondaryLockScreen); public void userActivity(); public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); @@ -504,9 +505,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe * @param authenticated true if the user entered the correct authentication * @param targetUserId a user that needs to be the foreground user at the finish (if called) * completion. + * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary + * secondary lock screen requirement, if any. * @return true if keyguard is done */ - boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) { + boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, + boolean bypassSecondaryLockScreen) { if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); boolean finish = false; boolean strongAuth = false; @@ -555,7 +559,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } } // Check for device admin specified additional security measures. - if (finish) { + if (finish && !bypassSecondaryLockScreen) { Intent secondaryLockscreenIntent = mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); if (secondaryLockscreenIntent != null) { @@ -636,8 +640,15 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe mUpdateMonitor.cancelFaceAuth(); } + @Override public void dismiss(boolean authenticated, int targetId) { - mSecurityCallback.dismiss(authenticated, targetId); + dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); + } + + @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { + mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); } public boolean isVerifyUnlockOnly() { @@ -689,6 +700,9 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe @Override public void dismiss(boolean securityVerified, int targetUserId) { } @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { } + @Override public void onUserInput() { } @Override public void reset() {} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 57e3f14d7aed..367058fa58dd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -57,7 +57,6 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; -import android.media.AudioManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.IRemoteCallback; @@ -82,6 +81,8 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; +import androidx.lifecycle.Observer; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; @@ -97,6 +98,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.util.Assert; +import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -223,6 +225,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mRingMode; private int mPhoneState; private boolean mKeyguardIsVisible; + private boolean mCredentialAttempted; private boolean mKeyguardGoingAway; private boolean mGoingToSleep; private boolean mBouncer; @@ -257,6 +260,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private TrustManager mTrustManager; private UserManager mUserManager; private KeyguardBypassController mKeyguardBypassController; + private RingerModeTracker mRingerModeTracker; private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private int mFaceRunningState = BIOMETRIC_STATE_STOPPED; private LockPatternUtils mLockPatternUtils; @@ -294,6 +298,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Handler mHandler; + private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() { + @Override + public void onChanged(Integer ringer) { + mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, ringer, 0).sendToTarget(); + } + }; + private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray(); private BiometricManager mBiometricManager; private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -498,11 +509,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Updates KeyguardUpdateMonitor's internal state to know if credential was attempted on + * bouncer. Note that this does not care if the credential was correct/incorrect. This is + * cleared when the user leaves the bouncer (unlocked, screen off, back to lockscreen, etc) + */ + public void setCredentialAttempted() { + mCredentialAttempted = true; + updateBiometricListeningState(); + } + + /** * Updates KeyguardUpdateMonitor's internal state to know if keyguard is goingAway */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; - updateFingerprintListeningState(); + updateBiometricListeningState(); } /** @@ -664,6 +685,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(); } else { setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); + mFingerprintCancelSignal = null; + mFaceCancelSignal = null; } if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) { @@ -679,6 +702,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab getCurrentUser()); } + if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT + || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + mFingerprintLockedOut = true; + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -688,6 +716,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void handleFingerprintLockoutReset() { + mFingerprintLockedOut = false; updateFingerprintListeningState(); } @@ -1126,9 +1155,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState) .sendToTarget(); - } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, - intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0)); } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); @@ -1274,6 +1300,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private CancellationSignal mFaceCancelSignal; private FingerprintManager mFpm; private FaceManager mFaceManager; + private boolean mFingerprintLockedOut; /** * When we receive a @@ -1481,6 +1508,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mUserTrustIsUsuallyManaged.delete(userId); } + private void registerRingerTracker() { + mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver); + } + @VisibleForTesting @Inject protected KeyguardUpdateMonitor( @@ -1488,6 +1519,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, DumpManager dumpManager, + RingerModeTracker ringerModeTracker, @Background Executor backgroundExecutor) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); @@ -1495,6 +1527,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); mBackgroundExecutor = backgroundExecutor; mBroadcastDispatcher = broadcastDispatcher; + mRingerModeTracker = ringerModeTracker; dumpManager.registerDumpable(getClass().getName(), this); mHandler = new Handler(mainLooper) { @@ -1628,10 +1661,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(Intent.ACTION_SERVICE_STATE); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); + mHandler.post(this::registerRingerTracker); + final IntentFilter allUserFilter = new IntentFilter(); allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); @@ -1820,13 +1854,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean shouldListenForFingerprint() { + final boolean allowedOnBouncer = + !(mFingerprintLockedOut && mBouncer && mCredentialAttempted); + // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. final boolean shouldListen = (mKeyguardIsVisible || !mDeviceInteractive || (mBouncer && !mKeyguardGoingAway) || mGoingToSleep || shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) - && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser; + && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser + && allowedOnBouncer; return shouldListen; } @@ -2372,6 +2410,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // camera requests dismiss keyguard (tapping on photos for example). When these happen, // face auth should resume. mSecureCameraLaunched = false; + } else { + mCredentialAttempted = false; } for (int i = 0; i < mCallbacks.size(); i++) { @@ -2776,6 +2816,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver); + mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver); mHandler.removeCallbacksAndMessages(null); } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java index 362014f51e36..e17d4e69093a 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java @@ -25,6 +25,7 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.time.SystemClock; /** * Extends the lifetime of foreground notification services such that they show for at least @@ -39,8 +40,10 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback; private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>(); private Handler mHandler = new Handler(Looper.getMainLooper()); + private final SystemClock mSystemClock; - public ForegroundServiceLifetimeExtender() { + public ForegroundServiceLifetimeExtender(SystemClock systemClock) { + mSystemClock = systemClock; } @Override @@ -55,8 +58,8 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx return false; } - long currentTime = System.currentTimeMillis(); - return currentTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS; + long currentTime = mSystemClock.uptimeMillis(); + return currentTime - entry.getCreationTime() < MIN_FGS_TIME_MS; } @Override @@ -84,7 +87,7 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx } }; long delayAmt = MIN_FGS_TIME_MS - - (System.currentTimeMillis() - entry.getSbn().getPostTime()); + - (mSystemClock.uptimeMillis() - entry.getCreationTime()); mHandler.postDelayed(r, delayAmt); } } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index f01fa811c529..ef1f4e0afede 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.util.time.SystemClock; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,7 +50,8 @@ public class ForegroundServiceNotificationListener { public ForegroundServiceNotificationListener(Context context, ForegroundServiceController foregroundServiceController, NotificationEntryManager notificationEntryManager, - NotifPipeline notifPipeline) { + NotifPipeline notifPipeline, + SystemClock systemClock) { mContext = context; mForegroundServiceController = foregroundServiceController; @@ -76,7 +78,8 @@ public class ForegroundServiceNotificationListener { removeNotification(entry.getSbn()); } }); - mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender()); + mEntryManager.addNotificationLifetimeExtender( + new ForegroundServiceLifetimeExtender(systemClock)); notifPipeline.addCollectionListener(new NotifCollectionListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 23fa645eff16..5442299881c0 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -120,7 +120,7 @@ public class ImageWallpaper extends WallpaperService { private void init(DozeParameters dozeParameters) { mIsHighEndGfx = ActivityManager.isHighEndGfx(); mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking(); - mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking; + mNeedTransition = false; // We will preserve EGL context when we are in lock screen or aod // to avoid janking in following transition, we need to release when back to home. @@ -137,7 +137,7 @@ public class ImageWallpaper extends WallpaperService { mRenderer = getRendererInstance(); getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo); setFixedSizeAllowed(true); - setOffsetNotificationsEnabled(true); + setOffsetNotificationsEnabled(mNeedTransition); updateSurfaceSize(); } diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index 6923079dd5c4..13d6a9bef266 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -49,6 +49,8 @@ public class Interpolators { public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator(); public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); + public static final Interpolator SHADE_ANIMATION = + new PathInterpolator(0.6f, 0.02f, 0.4f, 0.98f); public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f, 1.1f); public static final Interpolator PANEL_CLOSE_ACCELERATED diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index b006bc1351a3..8bf259182544 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -214,9 +214,11 @@ public abstract class AuthCredentialView extends LinearLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); - setText(mTitleView, getTitle(mBiometricPromptBundle)); + final CharSequence title = getTitle(mBiometricPromptBundle); + setText(mTitleView, title); setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle)); setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle)); + announceForAccessibility(title); final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId); final Drawable image; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9d885fd3c207..669a86b8a742 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -599,7 +599,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mStackView == null) { mStackView = new BubbleStackView( mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, - mSysUiState); + mSysUiState, mNotificationShadeWindowController); ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView(); int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble)); int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim. @@ -719,13 +719,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Tell the stack of bubbles to expand. - */ - public void expandStack() { - mBubbleData.setExpanded(true); - } - - /** * Tell the stack of bubbles to collapse. */ public void collapseStack() { @@ -753,12 +746,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed; } - @VisibleForTesting - void selectBubble(String key) { - Bubble bubble = mBubbleData.getBubbleWithKey(key); - mBubbleData.setSelectedBubble(bubble); - } - void promoteBubbleFromOverflow(Bubble bubble) { bubble.setInflateSynchronously(mInflateSynchronously); mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); @@ -778,13 +765,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. - */ - void dismissStack(@DismissReason int reason) { - mBubbleData.dismissAll(reason); - } - - /** * Directs a back gesture at the bubble stack. When opened, the current expanded bubble * is forwarded a back key down/up pair. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index be9cd5f01c86..4c149ddd3939 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -28,6 +28,7 @@ import android.content.Context; import android.service.notification.NotificationListenerService; import android.util.Log; import android.util.Pair; +import android.view.View; import androidx.annotation.Nullable; @@ -751,6 +752,7 @@ public class BubbleData { } @VisibleForTesting(visibility = PRIVATE) + @Nullable Bubble getBubbleWithKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); @@ -761,6 +763,17 @@ public class BubbleData { return null; } + @Nullable + Bubble getBubbleWithView(View view) { + for (int i = 0; i < mBubbles.size(); i++) { + Bubble bubble = mBubbles.get(i); + if (bubble.getIconView() != null && bubble.getIconView().equals(view)) { + return bubble; + } + } + return null; + } + @VisibleForTesting(visibility = PRIVATE) Bubble getOverflowBubbleWithKey(String key) { for (int i = 0; i < mOverflowBubbles.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 496456deccee..93fb6972fad5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.view.Display.INVALID_DISPLAY; @@ -126,6 +127,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), 0 /* enterResId */, 0 /* exitResId */); options.setTaskAlwaysOnTop(true); + options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); // Post to keep the lifecycle normal post(() -> { if (DEBUG_BUBBLE_EXPANDED_VIEW) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index eff693436451..61e6f39c054f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -30,6 +30,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.app.Notification; import android.content.Context; import android.content.res.Configuration; @@ -43,6 +44,7 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.os.Bundle; import android.os.Vibrator; import android.util.Log; @@ -81,8 +83,10 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; +import com.android.systemui.util.RelativeTouchListener; import com.android.systemui.util.animation.PhysicsAnimator; import com.android.systemui.util.magnetictarget.MagnetizedObject; @@ -239,7 +243,6 @@ public class BubbleStackView extends FrameLayout { mExpandedAnimationController.dump(fd, pw, args); } - private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; private SysUiState mSysUiState; @@ -296,7 +299,7 @@ public class BubbleStackView extends FrameLayout { @Override public void setValue(Object o, float v) { - onFlyoutDragged(v); + setFlyoutStateForDragLength(v); } }; @@ -328,6 +331,8 @@ public class BubbleStackView extends FrameLayout { @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer; + private final NotificationShadeWindowController mNotificationShadeWindowController; + /** * The currently magnetized object, which is being dragged and will be attracted to the magnetic * dismiss target. @@ -337,13 +342,6 @@ public class BubbleStackView extends FrameLayout { private MagnetizedObject<?> mMagnetizedObject; /** - * The action to run when the magnetized object is released in the dismiss target. - * - * This will actually perform the dismissal of either the stack or an individual bubble. - */ - private Runnable mReleasedInDismissTargetAction; - - /** * The MagneticTarget instance for our circular dismiss view. This is added to the * MagnetizedObject instances for the stack and any dragged-out bubbles. */ @@ -377,7 +375,7 @@ public class BubbleStackView extends FrameLayout { public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { mExpandedAnimationController.dismissDraggedOutBubble( mExpandedAnimationController.getDraggedOutBubble(), - mReleasedInDismissTargetAction); + BubbleStackView.this::dismissMagnetizedObject); hideDismissTarget(); } }; @@ -410,7 +408,7 @@ public class BubbleStackView extends FrameLayout { mStackAnimationController.implodeStack( () -> { resetDesaturationAndDarken(); - mReleasedInDismissTargetAction.run(); + dismissMagnetizedObject(); } ); @@ -418,6 +416,197 @@ public class BubbleStackView extends FrameLayout { } }; + /** + * Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack. + * When expanded, clicking a bubble either expands that bubble, or collapses the stack. + */ + private OnClickListener mBubbleClickListener = new OnClickListener() { + @Override + public void onClick(View view) { + final Bubble clickedBubble = mBubbleData.getBubbleWithView(view); + + // If the bubble has since left us, ignore the click. + if (clickedBubble == null) { + return; + } + + final boolean clickedBubbleIsCurrentlyExpandedBubble = + clickedBubble.getKey().equals(mExpandedBubble.getKey()); + + if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) { + if (clickedBubble != mBubbleData.getSelectedBubble()) { + // Select the clicked bubble. + mBubbleData.setSelectedBubble(clickedBubble); + } else { + // If the clicked bubble is the selected bubble (but not the expanded bubble), + // that means overflow was previously expanded. Set the selected bubble + // internally without going through BubbleData (which would ignore it since it's + // already selected). + setSelectedBubble(clickedBubble); + + } + } else { + // Otherwise, we either tapped the stack (which means we're collapsed + // and should expand) or the currently selected bubble (we're expanded + // and should collapse). + if (!maybeShowStackUserEducation()) { + mBubbleData.setExpanded(!mBubbleData.isExpanded()); + } + } + } + }; + + /** + * Touch listener set on each bubble view. This enables dragging and dismissing the stack (when + * collapsed), or individual bubbles (when expanded). + */ + private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() { + + @Override + public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { + // If we're expanding or collapsing, consume but ignore all touch events. + if (mIsExpansionAnimating) { + return true; + } + + if (mBubbleData.isExpanded()) { + maybeShowManageEducation(false /* show */); + + // If we're expanded, tell the animation controller to prepare to drag this bubble, + // dispatching to the individual bubble magnet listener. + mExpandedAnimationController.prepareForBubbleDrag( + v /* bubble */, + mMagneticTarget, + mIndividualBubbleMagnetListener); + + // Save the magnetized individual bubble so we can dispatch touch events to it. + mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); + } else { + // If we're collapsed, prepare to drag the stack. Cancel active animations, set the + // animation controller, and hide the flyout. + mStackAnimationController.cancelStackPositionAnimations(); + mBubbleContainer.setActiveController(mStackAnimationController); + hideFlyoutImmediate(); + + // Also, save the magnetized stack so we can dispatch touch events to it. + mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget); + mMagnetizedObject.setMagnetListener(mStackMagnetListener); + } + + passEventToMagnetizedObject(ev); + + // Bubbles are always interested in all touch events! + return true; + } + + @Override + public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy) { + // If we're expanding or collapsing, ignore all touch events. + if (mIsExpansionAnimating) { + return; + } + + // Show the dismiss target, if we haven't already. + springInDismissTargetMaybe(); + + // First, see if the magnetized object consumes the event - if so, we shouldn't move the + // bubble since it's stuck to the target. + if (!passEventToMagnetizedObject(ev)) { + if (mBubbleData.isExpanded()) { + mExpandedAnimationController.dragBubbleOut( + v, viewInitialX + dx, viewInitialY + dy); + } else { + hideStackUserEducation(false /* fromExpansion */); + mStackAnimationController.moveStackFromTouch( + viewInitialX + dx, viewInitialY + dy); + } + } + } + + @Override + public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy, float velX, float velY) { + // If we're expanding or collapsing, ignore all touch events. + if (mIsExpansionAnimating) { + return; + } + + // First, see if the magnetized object consumes the event - if so, the bubble was + // released in the target or flung out of it, and we should ignore the event. + if (!passEventToMagnetizedObject(ev)) { + if (mBubbleData.isExpanded()) { + mExpandedAnimationController.snapBubbleBack(v, velX, velY); + } else { + // Fling the stack to the edge, and save whether or not it's going to end up on + // the left side of the screen. + mStackOnLeftOrWillBe = + mStackAnimationController.flingStackThenSpringToEdge( + viewInitialX + dx, velX, velY) <= 0; + + updateBubbleZOrdersAndDotPosition(true /* animate */); + + logBubbleEvent(null /* no bubble associated with bubble stack move */, + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); + } + + hideDismissTarget(); + } + } + }; + + /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */ + private OnClickListener mFlyoutClickListener = new OnClickListener() { + @Override + public void onClick(View view) { + if (maybeShowStackUserEducation()) { + // If we're showing user education, don't open the bubble show the education first + mBubbleToExpandAfterFlyoutCollapse = null; + } else { + mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble(); + } + + mFlyout.removeCallbacks(mHideFlyout); + mHideFlyout.run(); + } + }; + + /** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */ + private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() { + + @Override + public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { + mFlyout.removeCallbacks(mHideFlyout); + return true; + } + + @Override + public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy) { + setFlyoutStateForDragLength(dx); + } + + @Override + public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy, float velX, float velY) { + final boolean onLeft = mStackAnimationController.isStackOnLeftSide(); + final boolean metRequiredVelocity = + onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY; + final boolean metRequiredDeltaX = + onLeft + ? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS + : dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS; + final boolean isCancelFling = onLeft ? velX > 0 : velX < 0; + final boolean shouldDismiss = metRequiredVelocity + || (metRequiredDeltaX && !isCancelFling); + + mFlyout.removeCallbacks(mHideFlyout); + animateFlyoutCollapsed(shouldDismiss, velX); + + maybeShowStackUserEducation(); + } + }; + private ViewGroup mDismissTargetContainer; private PhysicsAnimator<View> mDismissTargetAnimator; private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig( @@ -436,18 +625,19 @@ public class BubbleStackView extends FrameLayout { private BubbleManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; + @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, - SysUiState sysUiState) { + SysUiState sysUiState, + NotificationShadeWindowController notificationShadeWindowController) { super(context); mBubbleData = data; mInflater = LayoutInflater.from(context); - mTouchHandler = new BubbleTouchHandler(this, data, context); - setOnTouchListener(mTouchHandler); mSysUiState = sysUiState; + mNotificationShadeWindowController = notificationShadeWindowController; Resources res = getResources(); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); @@ -514,7 +704,7 @@ public class BubbleStackView extends FrameLayout { mDismissTargetContainer = new FrameLayout(context); mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams( MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height), + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), Gravity.BOTTOM)); mDismissTargetContainer.setClipChildren(false); mDismissTargetContainer.addView(targetView); @@ -523,7 +713,7 @@ public class BubbleStackView extends FrameLayout { // Start translated down so the target springs up. targetView.setTranslationY( - getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height)); + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height)); // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects. @@ -641,6 +831,18 @@ public class BubbleStackView extends FrameLayout { mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix)); mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint); }); + + // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts, + // ActivityViews, etc.) were touched. Collapse the stack if it's expanded. + setOnTouchListener((view, ev) -> { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (mBubbleData.isExpanded()) { + mBubbleData.setExpanded(false); + } + } + + return false; + }); } private void setUpUserEducation() { @@ -690,6 +892,7 @@ public class BubbleStackView extends FrameLayout { } } + @SuppressLint("ClickableViewAccessibility") private void setUpFlyout() { if (mFlyout != null) { removeView(mFlyout); @@ -699,6 +902,8 @@ public class BubbleStackView extends FrameLayout { mFlyout.animate() .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION) .setInterpolator(new AccelerateDecelerateInterpolator()); + mFlyout.setOnClickListener(mFlyoutClickListener); + mFlyout.setOnTouchListener(mFlyoutTouchListener); addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); } @@ -718,6 +923,7 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow)); } /** * Handle theme changes. @@ -920,6 +1126,7 @@ public class BubbleStackView extends FrameLayout { } // via BubbleData.Listener + @SuppressLint("ClickableViewAccessibility") void addBubble(Bubble bubble) { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "addBubble: " + bubble); @@ -944,6 +1151,9 @@ public class BubbleStackView extends FrameLayout { bubble.getIconView().setDotPositionOnLeft( !mStackOnLeftOrWillBe /* onLeft */, false /* animate */); + bubble.getIconView().setOnClickListener(mBubbleClickListener); + bubble.getIconView().setOnTouchListener(mBubbleTouchListener); + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); @@ -1009,10 +1219,6 @@ public class BubbleStackView extends FrameLayout { updatePointerPosition(); } - void showOverflow() { - setSelectedBubble(mBubbleOverflow); - } - /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the @@ -1177,14 +1383,6 @@ public class BubbleStackView extends FrameLayout { } } - /* - * Sets the action to run to dismiss the currently dragging object (either the stack or an - * individual bubble). - */ - public void setReleasedInDismissTargetAction(Runnable action) { - mReleasedInDismissTargetAction = action; - } - /** * Dismiss the stack of bubbles. * @@ -1201,54 +1399,6 @@ public class BubbleStackView extends FrameLayout { } /** - * @return the view the touch event is on - */ - @Nullable - public View getTargetView(MotionEvent event) { - float x = event.getRawX(); - float y = event.getRawY(); - if (mIsExpanded) { - if (isIntersecting(mBubbleContainer, x, y)) { - if (BubbleExperimentConfig.allowBubbleOverflow(mContext) - && isIntersecting(mBubbleOverflow.getBtn(), x, y)) { - return mBubbleOverflow.getBtn(); - } - // Could be tapping or dragging a bubble while expanded - for (int i = 0; i < getBubbleCount(); i++) { - BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); - if (isIntersecting(view, x, y)) { - return view; - } - } - } - BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0); - if (bev.intersectingTouchableContent((int) x, (int) y)) { - return bev; - } - // Outside of the parts we care about. - return null; - } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) { - return mFlyout; - } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) { - View bubbleChild = mBubbleContainer.getChildAt(0); - if (isIntersecting(bubbleChild, x, y)) { - return this; - } else if (isIntersecting(mUserEducationView, x, y)) { - return mUserEducationView; - } else { - return null; - } - } - - // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack. - return this; - } - - View getFlyoutView() { - return mFlyout; - } - - /** * @deprecated use {@link #setExpanded(boolean)} and * {@link BubbleData#setSelectedBubble(Bubble)} */ @@ -1385,124 +1535,74 @@ public class BubbleStackView extends FrameLayout { } } - /** Called when the collapsed stack is tapped on. */ - void onStackTapped() { - if (!maybeShowStackUserEducation()) { - mBubbleData.setExpanded(true); - } - } - - /** Called when a drag operation on an individual bubble has started. */ - public void onBubbleDragStart(View bubble) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "onBubbleDragStart: bubble=" + ((BadgedImageView) bubble).getKey()); - } - - if (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView())) { - return; - } - - mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget); - - // We're dragging an individual bubble, so set the magnetized object to the magnetized - // bubble. - mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); - mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener); - - maybeShowManageEducation(false); - } - - /** Called with the coordinates to which an individual bubble has been dragged. */ - public void onBubbleDragged(View bubble, float x, float y) { - if (!mIsExpanded || mIsExpansionAnimating - || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) { - return; - } - - mExpandedAnimationController.dragBubbleOut(bubble, x, y); - springInDismissTarget(); - } - - /** Called when a drag operation on an individual bubble has finished. */ - public void onBubbleDragFinish( - View bubble, float x, float y, float velX, float velY) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble); - } - - if (!mIsExpanded || mIsExpansionAnimating - || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) { - return; - } - - mExpandedAnimationController.snapBubbleBack(bubble, velX, velY); - hideDismissTarget(); - } - - /** Expands the clicked bubble. */ - public void expandBubble(Bubble bubble) { - if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) { - // If the bubble we're supposed to expand is the selected bubble, that means the - // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as - // selected, since it already is. Just call the stack's setSelectedBubble to expand it. - setSelectedBubble(bubble); - } else { - mBubbleData.setSelectedBubble(bubble); - } - } - - void onDragStart() { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "onDragStart()"); - } - if (mIsExpanded || mIsExpansionAnimating) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "mIsExpanded or mIsExpansionAnimating"); - } - return; + /** + * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a + * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV). + * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided + * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to + * the special nature of ActivityView, it does not respect the standard + * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for + * this purpose. + * + * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation + * properties for performance reasons. This means that the default implementation of this method + * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in + * it not receiving any touch events. This was previously addressed by returning false in the + * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any + * touch handlers in the stack or its child views. + * + * To support touch handlers, we're overriding this method to leave the ActivityView's touchable + * region alone. The only touchable part of the stack that can ever overlap the AV is a + * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually + * updating the touchable region to allow users to grab a bubble while it completes its ~50ms + * animation back to the bubble row. + * + * NOTE: Any future additions to the stack that obscure the ActivityView region will need their + * bounds subtracted here in order to receive touch events. + */ + @Override + public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { + // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch + // events from any location. + if (mNotificationShadeWindowController.getPanelExpanded()) { + touchableRegion.setEmpty(); } - mStackAnimationController.cancelStackPositionAnimations(); - mBubbleContainer.setActiveController(mStackAnimationController); - hideFlyoutImmediate(); - - // Since we're dragging the stack, set the magnetized object to the magnetized stack. - mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget); - mMagnetizedObject.setMagnetListener(mStackMagnetListener); } - void onDragged(float x, float y) { - if (mIsExpanded || mIsExpansionAnimating) { - return; - } - - hideStackUserEducation(false /* fromExpansion */); - springInDismissTarget(); - mStackAnimationController.moveStackFromTouch(x, y); + /** + * If you're here because you're not receiving touch events on a view that is a descendant of + * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the + * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView + * consumes all touch events within its bounds, even for views like the BubbleStackView that are + * above it. It ignores typical view touch handling methods like this one and + * dispatchTouchEvent. + */ + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return super.onInterceptTouchEvent(ev); } - void onDragFinish(float x, float y, float velX, float velY) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "onDragFinish"); - } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean dispatched = super.dispatchTouchEvent(ev); - if (mIsExpanded || mIsExpansionAnimating) { - return; + // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned + // at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will + // then be passed to the new bubble, which will not consume them since it hasn't received an + // ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler + // until the current gesture ends with an ACTION_UP event. + if (!dispatched && !mIsExpanded && mIsGestureInProgress) { + dispatched = mBubbleTouchListener.onTouch(this /* view */, ev); } - final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY); - logBubbleEvent(null /* no bubble associated with bubble stack move */, - SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); - - mStackOnLeftOrWillBe = newStackX <= 0; - updateBubbleZOrdersAndDotPosition(true /* animate */); - hideDismissTarget(); - } + mIsGestureInProgress = + ev.getAction() != MotionEvent.ACTION_UP + && ev.getAction() != MotionEvent.ACTION_CANCEL; - void onFlyoutDragStart() { - mFlyout.removeCallbacks(mHideFlyout); + return dispatched; } - void onFlyoutDragged(float deltaX) { + void setFlyoutStateForDragLength(float deltaX) { // This shouldn't happen, but if it does, just wait until the flyout lays out. This method // is continually called. if (mFlyout.getWidth() <= 0) { @@ -1538,59 +1638,27 @@ public class BubbleStackView extends FrameLayout { mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation); } - void onFlyoutTapped() { - if (maybeShowStackUserEducation()) { - // If we're showing user education, don't open the bubble show the education first - mBubbleToExpandAfterFlyoutCollapse = null; - } else { - mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble(); - } - - mFlyout.removeCallbacks(mHideFlyout); - mHideFlyout.run(); - } - - /** - * Called when the flyout drag has finished, and returns true if the gesture successfully - * dismissed the flyout. - */ - void onFlyoutDragFinished(float deltaX, float velX) { - final boolean onLeft = mStackAnimationController.isStackOnLeftSide(); - final boolean metRequiredVelocity = - onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY; - final boolean metRequiredDeltaX = - onLeft - ? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS - : deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS; - final boolean isCancelFling = onLeft ? velX > 0 : velX < 0; - final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling); - - mFlyout.removeCallbacks(mHideFlyout); - animateFlyoutCollapsed(shouldDismiss, velX); - - maybeShowStackUserEducation(); + /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ + private boolean passEventToMagnetizedObject(MotionEvent event) { + return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); } /** - * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.) - * is received. + * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the + * stack, if we're collapsed. */ - void onGestureStart() { - mIsGestureInProgress = true; - } - - /** Called when a gesture is completed or cancelled. */ - void onGestureFinished() { - mIsGestureInProgress = false; - + private void dismissMagnetizedObject() { if (mIsExpanded) { - mExpandedAnimationController.onGestureFinished(); - } - } + final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject(); + final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView); - /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ - boolean passEventToMagnetizedObject(MotionEvent event) { - return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); + if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) { + mBubbleData.notificationEntryRemoved( + draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); + } + } else { + mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); + } } /** Prepares and starts the desaturate/darken animation on the bubble stack. */ @@ -1624,7 +1692,7 @@ public class BubbleStackView extends FrameLayout { } /** Animates in the dismiss target. */ - private void springInDismissTarget() { + private void springInDismissTargetMaybe() { if (mShowingDismiss) { return; } @@ -1827,13 +1895,6 @@ public class BubbleStackView extends FrameLayout { return 0; } - private boolean isIntersecting(View view, float x, float y) { - mTempLoc = view.getLocationOnScreen(); - mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(), - mTempLoc[1] + view.getHeight()); - return mTempRect.contains(x, y); - } - private void requestUpdate() { if (mViewUpdatedRequested || mIsExpansionAnimating) { return; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java deleted file mode 100644 index 132c45fab3d2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2012 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.bubbles; - -import android.content.Context; -import android.graphics.PointF; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; - -import com.android.systemui.Dependency; - -/** - * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, - * dismissing, and flings. - */ -class BubbleTouchHandler implements View.OnTouchListener { - - private final PointF mTouchDown = new PointF(); - private final PointF mViewPositionOnTouchDown = new PointF(); - private final BubbleStackView mStack; - private final BubbleData mBubbleData; - - private BubbleController mController = Dependency.get(BubbleController.class); - - private boolean mMovedEnough; - private int mTouchSlopSquared; - private VelocityTracker mVelocityTracker; - - /** View that was initially touched, when we received the first ACTION_DOWN event. */ - private View mTouchedView; - - BubbleTouchHandler(BubbleStackView stackView, - BubbleData bubbleData, Context context) { - final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mTouchSlopSquared = touchSlop * touchSlop; - mBubbleData = bubbleData; - mStack = stackView; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - final int action = event.getActionMasked(); - - // If we aren't currently in the process of touching a view, figure out what we're touching. - // It'll be the stack, an individual bubble, or nothing. - if (mTouchedView == null) { - mTouchedView = mStack.getTargetView(event); - } - - // If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching - // anything, collapse the stack. - if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) { - mBubbleData.setExpanded(false); - mStack.hideStackUserEducation(false /* fromExpansion */); - resetForNextGesture(); - return false; - } - - if (!(mTouchedView instanceof BadgedImageView) - && !(mTouchedView instanceof BubbleStackView) - && !(mTouchedView instanceof BubbleFlyoutView)) { - - // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge - // of expanded view). - mStack.maybeShowManageEducation(false); - resetForNextGesture(); - return false; - } - - final boolean isStack = mStack.equals(mTouchedView); - final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView); - final float rawX = event.getRawX(); - final float rawY = event.getRawY(); - - // The coordinates of the touch event, in terms of the touched view's position. - final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x; - final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y; - switch (action) { - case MotionEvent.ACTION_DOWN: - trackMovement(event); - - mTouchDown.set(rawX, rawY); - mStack.onGestureStart(); - - if (isStack) { - mViewPositionOnTouchDown.set(mStack.getStackPosition()); - - // Dismiss the entire stack if it's released in the dismiss target. - mStack.setReleasedInDismissTargetAction( - () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE)); - mStack.onDragStart(); - mStack.passEventToMagnetizedObject(event); - } else if (isFlyout) { - mStack.onFlyoutDragStart(); - } else { - mViewPositionOnTouchDown.set( - mTouchedView.getTranslationX(), mTouchedView.getTranslationY()); - - // Dismiss only the dragged-out bubble if it's released in the target. - final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey(); - mStack.setReleasedInDismissTargetAction(() -> { - final Bubble bubble = - mBubbleData.getBubbleWithKey(individualBubbleKey); - // bubble can be null if the user is in the middle of - // dismissing the bubble, but the app also sent a cancel - if (bubble != null) { - mController.removeBubble(bubble.getEntry(), - BubbleController.DISMISS_USER_GESTURE); - } - }); - - mStack.onBubbleDragStart(mTouchedView); - mStack.passEventToMagnetizedObject(event); - } - - break; - case MotionEvent.ACTION_MOVE: - trackMovement(event); - final float deltaX = rawX - mTouchDown.x; - final float deltaY = rawY - mTouchDown.y; - - if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) { - mMovedEnough = true; - } - - if (mMovedEnough) { - if (isFlyout) { - mStack.onFlyoutDragged(deltaX); - } else if (!mStack.passEventToMagnetizedObject(event)) { - // If the magnetic target doesn't consume the event, drag the stack or - // bubble. - if (isStack) { - mStack.onDragged(viewX, viewY); - } else { - mStack.onBubbleDragged(mTouchedView, viewX, viewY); - } - } - } - break; - - case MotionEvent.ACTION_CANCEL: - resetForNextGesture(); - break; - - case MotionEvent.ACTION_UP: - trackMovement(event); - mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000); - final float velX = mVelocityTracker.getXVelocity(); - final float velY = mVelocityTracker.getYVelocity(); - - if (isFlyout && mMovedEnough) { - mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX); - } else if (isFlyout) { - if (!mBubbleData.isExpanded() && !mMovedEnough) { - mStack.onFlyoutTapped(); - } - } else if (mMovedEnough) { - if (!mStack.passEventToMagnetizedObject(event)) { - // If the magnetic target didn't consume the event, tell the stack to finish - // the drag. - if (isStack) { - mStack.onDragFinish(viewX, viewY, velX, velY); - } else { - mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY); - } - } - } else if (mTouchedView == mStack.getExpandedBubbleView()) { - mBubbleData.setExpanded(false); - } else if (isStack) { - mStack.onStackTapped(); - } else { - final String key = ((BadgedImageView) mTouchedView).getKey(); - if (key == BubbleOverflow.KEY) { - mStack.showOverflow(); - } else { - mStack.expandBubble(mBubbleData.getBubbleWithKey(key)); - } - } - resetForNextGesture(); - break; - } - - return true; - } - - /** Clears all touch-related state. */ - private void resetForNextGesture() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - - mTouchedView = null; - mMovedEnough = false; - - mStack.onGestureFinished(); - } - - private void trackMovement(MotionEvent event) { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index ea1abf99a0f3..d974adc34ee0 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -252,8 +252,14 @@ public class ExpandedAnimationController mSpringToTouchOnNextMotionEvent = true; } - /** Prepares the given bubble to be dragged out. */ - public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) { + /** + * Prepares the given bubble view to be dragged out, using the provided magnetic target and + * listener. + */ + public void prepareForBubbleDrag( + View bubble, + MagnetizedObject.MagneticTarget target, + MagnetizedObject.MagnetListener listener) { mLayout.cancelAnimationsOnView(bubble); bubble.setTranslationZ(Short.MAX_VALUE); @@ -277,6 +283,7 @@ public class ExpandedAnimationController } }; mMagnetizedBubbleDraggingOut.addTarget(target); + mMagnetizedBubbleDraggingOut.setMagnetListener(listener); mMagnetizedBubbleDraggingOut.setHapticsEnabled(true); mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); } @@ -323,6 +330,9 @@ public class ExpandedAnimationController /** Plays a dismiss animation on the dragged out bubble. */ public void dismissDraggedOutBubble(View bubble, Runnable after) { + if (bubble == null) { + return; + } animationForChild(bubble) .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index c292769f1066..b1bbafc1ed8f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -1117,9 +1117,4 @@ public class PhysicsAnimationLayout extends FrameLayout { mAssociatedController = controller; } } - - @Override - protected boolean canReceivePointerEvents() { - return false; - } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 5d03fc51004d..7e8fec716b1f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -45,7 +45,7 @@ open class ControlsBindingControllerImpl @Inject constructor( companion object { private const val TAG = "ControlsBindingControllerImpl" private const val MAX_CONTROLS_REQUEST = 100000L - private const val SUGGESTED_CONTROLS_REQUEST = 4L + private const val SUGGESTED_CONTROLS_REQUEST = 6L } private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) @@ -61,6 +61,11 @@ open class ControlsBindingControllerImpl @Inject constructor( */ private var statefulControlSubscriber: StatefulControlSubscriber? = null + /* + * Will track any active load subscriber. Only one can be active at any time. + */ + private var loadSubscriber: LoadSubscriber? = null + private val actionCallbackService = object : IControlsActionCallback.Stub() { override fun accept( token: IBinder, @@ -99,17 +104,24 @@ open class ControlsBindingControllerImpl @Inject constructor( component: ComponentName, callback: ControlsBindingController.LoadCallback ): Runnable { - val subscriber = LoadSubscriber(callback, MAX_CONTROLS_REQUEST) - retrieveLifecycleManager(component).maybeBindAndLoad(subscriber) - return subscriber.loadCancel() + loadSubscriber?.loadCancel() + + val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST) + loadSubscriber = ls + + retrieveLifecycleManager(component).maybeBindAndLoad(ls) + return ls.loadCancel() } override fun bindAndLoadSuggested( component: ComponentName, callback: ControlsBindingController.LoadCallback ) { - val subscriber = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST) - retrieveLifecycleManager(component).maybeBindAndLoadSuggested(subscriber) + loadSubscriber?.loadCancel() + val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST) + loadSubscriber = ls + + retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls) } override fun subscribe(structureInfo: StructureInfo) { @@ -152,13 +164,16 @@ open class ControlsBindingControllerImpl @Inject constructor( override fun changeUser(newUser: UserHandle) { if (newUser == currentUser) return - unsubscribe() unbind() - currentProvider = null currentUser = newUser } private fun unbind() { + unsubscribe() + + loadSubscriber?.loadCancel() + loadSubscriber = null + currentProvider?.unbindService() currentProvider = null } @@ -210,6 +225,20 @@ open class ControlsBindingControllerImpl @Inject constructor( val callback: ControlsBindingController.LoadCallback ) : CallbackRunnable(token) { override fun doRun() { + Log.d(TAG, "LoadSubscription: Complete and loading controls") + callback.accept(list) + } + } + + private inner class OnCancelAndLoadRunnable( + token: IBinder, + val list: List<Control>, + val subscription: IControlsSubscription, + val callback: ControlsBindingController.LoadCallback + ) : CallbackRunnable(token) { + override fun doRun() { + Log.d(TAG, "LoadSubscription: Canceling and loading controls") + provider?.cancelSubscription(subscription) callback.accept(list) } } @@ -220,6 +249,7 @@ open class ControlsBindingControllerImpl @Inject constructor( val requestLimit: Long ) : CallbackRunnable(token) { override fun doRun() { + Log.d(TAG, "LoadSubscription: Starting subscription") provider?.startSubscription(subscription, requestLimit) } } @@ -254,34 +284,54 @@ open class ControlsBindingControllerImpl @Inject constructor( val requestLimit: Long ) : IControlsSubscriber.Stub() { val loadedControls = ArrayList<Control>() - var hasError = false + private var isTerminated = false private var _loadCancelInternal: (() -> Unit)? = null + private lateinit var subscription: IControlsSubscription + fun loadCancel() = Runnable { - Log.d(TAG, "Cancel load requested") - _loadCancelInternal?.invoke() - } + Log.d(TAG, "Cancel load requested") + _loadCancelInternal?.invoke() + } override fun onSubscribe(token: IBinder, subs: IControlsSubscription) { - _loadCancelInternal = subs::cancel + subscription = subs + _loadCancelInternal = { currentProvider?.cancelSubscription(subscription) } backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit)) } override fun onNext(token: IBinder, c: Control) { - backgroundExecutor.execute { loadedControls.add(c) } + backgroundExecutor.execute { + if (isTerminated) return@execute + + loadedControls.add(c) + + // Once we have reached our requestLimit, send a request to cancel, and immediately + // load the results. Calls to onError() and onComplete() are not required after + // cancel. + if (loadedControls.size >= requestLimit) { + maybeTerminateAndRun( + OnCancelAndLoadRunnable(token, loadedControls, subscription, callback) + ) + } + } } + override fun onError(token: IBinder, s: String) { - hasError = true - _loadCancelInternal = {} - currentProvider?.cancelLoadTimeout() - backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback)) + maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback)) } override fun onComplete(token: IBinder) { + maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback)) + } + + private fun maybeTerminateAndRun(postTerminateFn: Runnable) { + if (isTerminated) return + + isTerminated = true _loadCancelInternal = {} - if (!hasError) { - currentProvider?.cancelLoadTimeout() - backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback)) - } + currentProvider?.cancelLoadTimeout() + + backgroundExecutor.execute(postTerminateFn) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index ae75dd4d94ab..568fb289027d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -180,6 +180,11 @@ interface ControlsController : UserAwareController { fun countFavoritesForComponent(componentName: ComponentName): Int /** + * TEMPORARY for testing + */ + fun resetFavorites() + + /** * Interface for structure to pass data to [ControlsFavoritingActivity]. */ interface LoadData { diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 34833396acef..8805694616a4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -365,6 +365,8 @@ class ControlsControllerImpl @Inject constructor ( componentName: ComponentName, callback: Consumer<Boolean> ) { + if (seedingInProgress) return + Log.i(TAG, "Beginning request to seed favorites for: $componentName") if (!confirmAvailability()) { if (userChanging) { @@ -495,6 +497,13 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun resetFavorites() { + executor.execute { + Favorites.clear() + persistenceWrapper.storeFavorites(Favorites.getAllStructures()) + } + } + override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 895f1d218982..a6af6a11d8b7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -63,8 +63,6 @@ class ControlsProviderLifecycleManager( ) : IBinder.DeathRecipient { val token: IBinder = Binder() - @GuardedBy("subscriptions") - private val subscriptions = mutableListOf<IControlsSubscription>() private var requiresBound = false @GuardedBy("queuedServiceMethods") private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet() @@ -194,7 +192,7 @@ class ControlsProviderLifecycleManager( * Request a call to [IControlsProvider.loadSuggested]. * * If the service is not bound, the call will be queued and the service will be bound first. - * The service will be unbound after the controls are returned or the call times out. + * The service will be unbound if the call times out. * * @param subscriber the subscriber that manages coordination for loading controls */ @@ -245,9 +243,7 @@ class ControlsProviderLifecycleManager( if (DEBUG) { Log.d(TAG, "startSubscription: $subscription") } - synchronized(subscriptions) { - subscriptions.add(subscription) - } + wrapper?.request(subscription, requestLimit) } @@ -261,9 +257,7 @@ class ControlsProviderLifecycleManager( if (DEBUG) { Log.d(TAG, "cancelSubscription: $subscription") } - synchronized(subscriptions) { - subscriptions.remove(subscription) - } + wrapper?.cancel(subscription) } @@ -281,17 +275,6 @@ class ControlsProviderLifecycleManager( onLoadCanceller?.run() onLoadCanceller = null - // be sure to cancel all subscriptions - val subs = synchronized(subscriptions) { - ArrayList(subscriptions).also { - subscriptions.clear() - } - } - - subs.forEach { - wrapper?.cancel(it) - } - bindService(false) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index f2303e622f8d..fe1e6328820d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -55,13 +55,19 @@ class ControlsFavoritingActivity @Inject constructor( companion object { private const val TAG = "ControlsFavoritingActivity" + + // If provided and no structure is available, use as the title const val EXTRA_APP = "extra_app_label" + + // If provided, show this structure page first + const val EXTRA_STRUCTURE = "extra_structure" private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT private const val TOOLTIP_MAX_SHOWN = 2 } private var component: ComponentName? = null private var appName: CharSequence? = null + private var structureExtra: CharSequence? = null private lateinit var structurePager: ViewPager2 private lateinit var statusText: TextView @@ -111,6 +117,7 @@ class ControlsFavoritingActivity @Inject constructor( val collator = Collator.getInstance(resources.configuration.locales[0]) comparator = compareBy(collator) { it.structureName } appName = intent.getCharSequenceExtra(EXTRA_APP) + structureExtra = intent.getCharSequenceExtra(EXTRA_STRUCTURE) ?: "" component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) bindViews() @@ -137,9 +144,15 @@ class ControlsFavoritingActivity @Inject constructor( listOfStructures = controlsByStructure.map { StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString)) }.sortedWith(comparator) + + val structureIndex = listOfStructures.indexOfFirst { + sc -> sc.structureName == structureExtra + }.let { if (it == -1) 0 else it } + executor.execute { doneButton.isEnabled = true structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getText(R.string.controls_favorite_load_error) } else { @@ -247,7 +260,10 @@ class ControlsFavoritingActivity @Inject constructor( requireViewById<Button>(R.id.other_apps).apply { visibility = View.VISIBLE setOnClickListener { - this@ControlsFavoritingActivity.onBackPressed() + val i = Intent() + i.setComponent( + ComponentName(context, ControlsProviderSelectorActivity::class.java)) + context.startActivity(i) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 7d3a86091869..b1cb04e10eef 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -51,7 +51,6 @@ class ControlViewHolder( ) { val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) - val statusExtra: TextView = layout.requireViewById(R.id.status_extra) val title: TextView = layout.requireViewById(R.id.title) val subtitle: TextView = layout.requireViewById(R.id.subtitle) val context: Context = layout.getContext() @@ -65,6 +64,8 @@ class ControlViewHolder( val ld = layout.getBackground() as LayerDrawable ld.mutate() clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable + // needed for marquee to start + status.setSelected(true) } fun bindData(cws: ControlWithState) { @@ -103,8 +104,7 @@ class ControlViewHolder( behavior?.bind(cws) - layout.setContentDescription( - "${title.text} ${subtitle.text} ${status.text} ${statusExtra.text}") + layout.setContentDescription("${title.text} ${subtitle.text} ${status.text}") } fun actionResponse(@ControlAction.ResponseResult response: Int) { @@ -113,15 +113,12 @@ class ControlViewHolder( fun setTransientStatus(tempStatus: String) { val previousText = status.getText() - val previousTextExtra = statusExtra.getText() cancelUpdate = uiExecutor.executeDelayed({ status.setText(previousText) - statusExtra.setText(previousTextExtra) }, UPDATE_DELAY_IN_MILLIS) status.setText(tempStatus) - statusExtra.setText("") } fun action(action: ControlAction) { @@ -154,7 +151,6 @@ class ControlViewHolder( } status.setTextColor(fg) - statusExtra.setTextColor(fg) icon.setImageDrawable(ri.icon) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 05a0c45c2e15..7da3d70271c1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -16,16 +16,26 @@ package com.android.systemui.controls.ui +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.app.AlertDialog import android.app.Dialog import android.content.ComponentName import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences +import android.content.res.Configuration import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable +import android.os.Process import android.service.controls.Control import android.service.controls.actions.ControlAction +import android.util.TypedValue import android.util.Log +import android.view.animation.AccelerateInterpolator +import android.view.animation.DecelerateInterpolator import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View @@ -43,6 +53,7 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.dagger.qualifiers.Background @@ -74,6 +85,8 @@ class ControlsUiControllerImpl @Inject constructor ( private const val PREF_COMPONENT = "controls_component" private const val PREF_STRUCTURE = "controls_structure" + private const val FADE_IN_MILLIS = 225L + private val EMPTY_COMPONENT = ComponentName("", "") private val EMPTY_STRUCTURE = StructureInfo( EMPTY_COMPONENT, @@ -90,21 +103,8 @@ class ControlsUiControllerImpl @Inject constructor ( private lateinit var lastItems: List<SelectionItem> private var popup: ListPopupWindow? = null private var activeDialog: Dialog? = null - private val addControlsItem: SelectionItem private var hidden = true - init { - val addDrawable = context.getDrawable(R.drawable.ic_add).apply { - setTint(context.resources.getColor(R.color.control_secondary_text, null)) - } - addControlsItem = SelectionItem( - context.resources.getString(R.string.controls_providers_title), - "", - addDrawable, - EMPTY_COMPONENT - ) - } - override val available: Boolean get() = controlsController.get().available @@ -163,7 +163,20 @@ class ControlsUiControllerImpl @Inject constructor ( private fun reload(parent: ViewGroup) { if (hidden) return - show(parent) + + val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f) + fadeAnim.setInterpolator(AccelerateInterpolator(1.0f)) + fadeAnim.setDuration(FADE_IN_MILLIS) + fadeAnim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + show(parent) + val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) + showAnim.setInterpolator(DecelerateInterpolator(1.0f)) + showAnim.setDuration(FADE_IN_MILLIS) + showAnim.start() + } + }) + fadeAnim.start() } private fun showSeedingView(items: List<SelectionItem>) { @@ -182,7 +195,7 @@ class ControlsUiControllerImpl @Inject constructor ( inflater.inflate(R.layout.controls_no_favorites, parent, true) val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup - viewGroup.setOnClickListener(launchSelectorActivityListener(context)) + viewGroup.setOnClickListener { v: View -> startProviderSelectorActivity(v.context) } val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle) subtitle.setText(context.resources.getString(R.string.quick_controls_subtitle)) @@ -196,16 +209,28 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun launchSelectorActivityListener(context: Context): (View) -> Unit { - return { _ -> - val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - context.sendBroadcast(closeDialog) + private fun startFavoritingActivity(context: Context, si: StructureInfo) { + val i = Intent(context, ControlsFavoritingActivity::class.java).apply { + putExtra(ControlsFavoritingActivity.EXTRA_APP, + controlsListingController.get().getAppLabel(si.componentName)) + putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure) + putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(context, i) + } - val i = Intent() - i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) - i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) + private fun startProviderSelectorActivity(context: Context) { + val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) } + startActivity(context, i) + } + + private fun startActivity(context: Context, intent: Intent) { + val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(closeDialog) + context.startActivity(intent) } private fun showControlsView(items: List<SelectionItem>) { @@ -227,7 +252,8 @@ class ControlsUiControllerImpl @Inject constructor ( private fun createMenu() { val items = arrayOf( - context.resources.getString(R.string.controls_menu_add) + context.resources.getString(R.string.controls_menu_add), + "Reset" ) var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) @@ -246,7 +272,9 @@ class ControlsUiControllerImpl @Inject constructor ( ) { when (pos) { // 0: Add Control - 0 -> launchSelectorActivityListener(view.context)(parent) + 0 -> startFavoritingActivity(view.context, selectedStructure) + // 1: TEMPORARY for reset controls + 1 -> showResetConfirmation() else -> Log.w(ControlsUiController.TAG, "Unsupported index ($pos) on 'more' menu selection") } @@ -273,6 +301,39 @@ class ControlsUiControllerImpl @Inject constructor ( }) } + private fun showResetConfirmation() { + val builder = AlertDialog.Builder( + context, + android.R.style.Theme_DeviceDefault_Dialog_Alert + ).apply { + setMessage("For testing purposes: Would you like to " + + "reset your favorited device controls?") + setPositiveButton( + android.R.string.ok, + DialogInterface.OnClickListener { dialog, _ -> + val userHandle = Process.myUserHandle() + val userContext = context.createContextAsUser(userHandle, 0) + val prefs = userContext.getSharedPreferences( + "controls_prefs", Context.MODE_PRIVATE) + prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply() + controlsController.get().resetFavorites() + dialog.dismiss() + context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + }) + setNegativeButton( + android.R.string.cancel, + DialogInterface.OnClickListener { + dialog, _ -> dialog.cancel() + } + ) + } + builder.create().apply { + getWindow().apply { + setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + } + }.show() + } + private fun createDropDown(items: List<SelectionItem>) { items.forEach { RenderInfo.registerComponentIcon(it.componentName, it.icon) @@ -300,7 +361,6 @@ class ControlsUiControllerImpl @Inject constructor ( .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) } parent.requireViewById<ImageView>(R.id.app_icon).apply { - setContentDescription(selectionItem.getTitle()) setImageDrawable(selectionItem.icon) } @@ -345,10 +405,12 @@ class ControlsUiControllerImpl @Inject constructor ( val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_with_favorites, parent, true) + val maxColumns = findMaxColumns() + val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup var lastRow: ViewGroup = createRow(inflater, listView) selectedStructure.controls.forEach { - if (lastRow.getChildCount() == 2) { + if (lastRow.getChildCount() == maxColumns) { lastRow = createRow(inflater, listView) } val baseLayout = inflater.inflate( @@ -365,10 +427,39 @@ class ControlsUiControllerImpl @Inject constructor ( controlViewsById.put(key, cvh) } - // add spacer if necessary to keep control size consistent - if ((selectedStructure.controls.size % 2) == 1) { + // add spacers if necessary to keep control size consistent + val mod = selectedStructure.controls.size % maxColumns + var spacersToAdd = if (mod == 0) 0 else maxColumns - mod + while (spacersToAdd > 0) { lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f)) + spacersToAdd-- + } + } + + /** + * For low-dp width screens that also employ an increased font scale, adjust the + * number of columns. This helps prevent text truncation on these devices. + */ + private fun findMaxColumns(): Int { + val res = context.resources + var maxColumns = res.getInteger(R.integer.controls_max_columns) + val maxColumnsAdjustWidth = + res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) + + val outValue = TypedValue() + res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true) + val maxColumnsAdjustFontScale = outValue.getFloat() + + val config = res.configuration + val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT + if (isPortrait && + config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED && + config.screenWidthDp <= maxColumnsAdjustWidth && + config.fontScale >= maxColumnsAdjustFontScale) { + maxColumns-- } + + return maxColumns } private fun loadPreference(structures: List<StructureInfo>): StructureInfo { @@ -494,7 +585,6 @@ private class ItemAdapter( setText(item.getTitle()) } view.requireViewById<ImageView>(R.id.app_icon).apply { - setContentDescription(item.appName) setImageDrawable(item.icon) } return view diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index d33cd94004fd..124df32af5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -56,20 +56,19 @@ data class RenderInfo( enabled: Boolean, offset: Int = 0 ): RenderInfo { - val (fg, bg) = deviceColorMap.getValue(deviceType) - - val iconKey = if (offset > 0) { + val key = if (offset > 0) { deviceType * BUCKET_SIZE + offset } else deviceType - val iconState = deviceIconMap.getValue(iconKey) + val (fg, bg) = deviceColorMap.getValue(key) + val iconState = deviceIconMap.getValue(key) val resourceId = iconState[enabled] var icon: Drawable? if (resourceId == APP_ICON_ID) { icon = appIconMap.get(componentName) if (icon == null) { icon = context.resources - .getDrawable(R.drawable.ic_device_unknown_gm2_24px, null) + .getDrawable(R.drawable.ic_device_unknown_on, null) appIconMap.put(componentName, icon) } } else { @@ -268,10 +267,74 @@ private val deviceIconMap = mapOf<Int, IconState>( DeviceTypes.TYPE_ROUTINE to IconState( RenderInfo.APP_ICON_ID, RenderInfo.APP_ICON_ID + ), + DeviceTypes.TYPE_AC_HEATER to IconState( + R.drawable.ic_device_thermostat_off, + R.drawable.ic_device_thermostat_on + ), + DeviceTypes.TYPE_AC_UNIT to IconState( + R.drawable.ic_device_thermostat_off, + R.drawable.ic_device_thermostat_on + ), + DeviceTypes.TYPE_COFFEE_MAKER to IconState( + R.drawable.ic_device_kettle_off, + R.drawable.ic_device_kettle_on + ), + DeviceTypes.TYPE_DEHUMIDIFIER to IconState( + R.drawable.ic_device_air_freshener_off, + R.drawable.ic_device_air_freshener_on + ), + DeviceTypes.TYPE_RADIATOR to IconState( + R.drawable.ic_device_thermostat_off, + R.drawable.ic_device_thermostat_on + ), + DeviceTypes.TYPE_STANDMIXER to IconState( + R.drawable.ic_device_cooking_off, + R.drawable.ic_device_cooking_on + ), + DeviceTypes.TYPE_DISPLAY to IconState( + R.drawable.ic_device_display_off, + R.drawable.ic_device_display_on + ), + DeviceTypes.TYPE_DRYER to IconState( + R.drawable.ic_device_washer_off, + R.drawable.ic_device_washer_on + ), + DeviceTypes.TYPE_MOWER to IconState( + R.drawable.ic_device_outdoor_garden_off, + R.drawable.ic_device_outdoor_garden_on + ), + DeviceTypes.TYPE_SHOWER to IconState( + R.drawable.ic_device_water_off, + R.drawable.ic_device_water_on + ), + DeviceTypes.TYPE_AWNING to IconState( + R.drawable.ic_device_pergola_off, + R.drawable.ic_device_pergola_on + ), + DeviceTypes.TYPE_CLOSET to IconState( + R.drawable.ic_device_drawer_off, + R.drawable.ic_device_drawer_on + ), + DeviceTypes.TYPE_CURTAIN to IconState( + R.drawable.ic_device_blinds_off, + R.drawable.ic_device_blinds_on + ), + DeviceTypes.TYPE_DOOR to IconState( + R.drawable.ic_device_door_off, + R.drawable.ic_device_door_on + ), + DeviceTypes.TYPE_SHUTTER to IconState( + R.drawable.ic_device_window_off, + R.drawable.ic_device_window_on + ), + DeviceTypes.TYPE_HEATER to IconState( + R.drawable.ic_device_thermostat_off, + R.drawable.ic_device_thermostat_on ) ).withDefault { IconState( - R.drawable.ic_device_unknown_gm2_24px, - R.drawable.ic_device_unknown_gm2_24px + R.drawable.ic_device_unknown_off, + R.drawable.ic_device_unknown_on ) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt index f79c8b2393d0..5a956653285c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt @@ -47,9 +47,10 @@ class ToggleRangeBehavior : Behavior { lateinit var control: Control lateinit var cvh: ControlViewHolder lateinit var rangeTemplate: RangeTemplate - lateinit var statusExtra: TextView lateinit var status: TextView lateinit var context: Context + var currentStatusText: CharSequence = "" + var currentRangeValue: String = "" companion object { private const val DEFAULT_FORMAT = "%.1f" @@ -83,8 +84,8 @@ class ToggleRangeBehavior : Behavior { override fun bind(cws: ControlWithState) { this.control = cws.control!! - statusExtra = cvh.statusExtra - status.setText(control.getStatusText()) + currentStatusText = control.getStatusText() + status.setText(currentStatusText) val ld = cvh.layout.getBackground() as LayerDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) @@ -96,7 +97,7 @@ class ToggleRangeBehavior : Behavior { val checked = template.isChecked() val currentRatio = rangeTemplate.getCurrentValue() / (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()) - updateRange(currentRatio, checked) + updateRange(currentRatio, checked, /* isDragging */ false) cvh.applyRenderInfo(checked) @@ -147,7 +148,7 @@ class ToggleRangeBehavior : Behavior { AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE) val ratioDiff = (value - rangeTemplate.getCurrentValue()) / (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()) - updateRange(ratioDiff, template.isChecked()) + updateRange(ratioDiff, template.isChecked(), /* isDragging */ false) endUpdateRange() true } @@ -167,26 +168,27 @@ class ToggleRangeBehavior : Behavior { } fun beginUpdateRange() { - status.setVisibility(View.GONE) - statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) } - fun updateRange(ratioDiff: Float, checked: Boolean) { + fun updateRange(ratioDiff: Float, checked: Boolean, isDragging: Boolean) { val changeAmount = if (checked) (MAX_LEVEL * ratioDiff).toInt() else MIN_LEVEL val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, clipLayer.getLevel() + changeAmount)) clipLayer.setLevel(newLevel) if (checked) { val newValue = levelToRangeValue(clipLayer.getLevel()) - val formattedNewValue = format(rangeTemplate.getFormatString().toString(), + currentRangeValue = format(rangeTemplate.getFormatString().toString(), DEFAULT_FORMAT, newValue) - - statusExtra.setText(formattedNewValue) - statusExtra.setVisibility(View.VISIBLE) + val text = if (isDragging) { + currentRangeValue + } else { + "$currentStatusText $currentRangeValue" + } + status.setText(text) } else { - statusExtra.setText("") - statusExtra.setVisibility(View.GONE) + status.setText(currentStatusText) } } @@ -210,9 +212,9 @@ class ToggleRangeBehavior : Behavior { } fun endUpdateRange() { - statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() .getDimensionPixelSize(R.dimen.control_status_normal).toFloat()) - status.setVisibility(View.VISIBLE) + status.setText("$currentStatusText $currentRangeValue") cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(levelToRangeValue(clipLayer.getLevel())))) } @@ -260,7 +262,8 @@ class ToggleRangeBehavior : Behavior { isDragging = true } - this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(), true) + this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(), + /* checked */ true, /* isDragging */ true) return true } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java index 5b3d5c565472..82ccb17a52c6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java @@ -74,6 +74,8 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerServiceImpl; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.RingerModeTrackerImpl; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; import com.android.systemui.volume.VolumeDialogControllerImpl; @@ -264,4 +266,10 @@ public abstract class DependencyBinder { @Binds public abstract VolumeComponent provideVolumeComponent( VolumeDialogComponent volumeDialogComponent); + + /** + */ + @Binds + public abstract RingerModeTracker provideRingerModeTracker( + RingerModeTrackerImpl ringerModeTrackerImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java index c16dce12041d..3f88f252bfe7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java @@ -40,7 +40,7 @@ public class DozeDockHandler implements DozeMachine.Part { private int mDockState = DockManager.STATE_NONE; - public DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine, + DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine, DockManager dockManager) { mMachine = machine; mConfig = config; @@ -74,8 +74,13 @@ public class DozeDockHandler implements DozeMachine.Part { @Override public void onEvent(int dockState) { if (DEBUG) Log.d(TAG, "dock event = " + dockState); - final DozeMachine.State nextState; + mDockState = dockState; + if (isPulsing()) { + return; + } + + DozeMachine.State nextState; switch (mDockState) { case DockManager.STATE_DOCKED: nextState = State.DOZE_AOD_DOCKED; @@ -90,10 +95,15 @@ public class DozeDockHandler implements DozeMachine.Part { default: return; } - mMachine.requestState(nextState); } + private boolean isPulsing() { + DozeMachine.State state = mMachine.getState(); + return state == State.DOZE_REQUEST_PULSE || state == State.DOZE_PULSING + || state == State.DOZE_PULSING_BRIGHT; + } + void register() { if (mRegistered) { return; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index f7f9afdd2928..18bfd899a4e7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -339,8 +339,8 @@ public class DozeMachine { return State.DOZE; } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING - || mState == State.DOZE_AOD || mState == State.DOZE) - && requestedState == State.DOZE_PULSE_DONE) { + || mState == State.DOZE_AOD || mState == State.DOZE + || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) { Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); return mState; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index fdd859373685..a4a589481b33 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -39,9 +39,11 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.UserInfo; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -70,6 +72,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; @@ -77,6 +80,11 @@ import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; + import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; @@ -113,6 +121,7 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.EmergencyDialerConstants; +import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; @@ -129,7 +138,8 @@ import javax.inject.Inject; public class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener, - GlobalActionsPanelPlugin.Callbacks { + GlobalActionsPanelPlugin.Callbacks, + LifecycleOwner { public static final String SYSTEM_DIALOG_REASON_KEY = "reason"; public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; @@ -178,6 +188,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final NotificationShadeDepthController mDepthController; private final BlurUtils mBlurUtils; + // Used for RingerModeTracker + private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -188,7 +201,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; - private ToggleAction.State mAirplaneState = ToggleAction.State.Off; + private ToggleState mAirplaneState = ToggleState.Off; private boolean mIsWaitingForEcmExit = false; private boolean mHasTelephony; private boolean mHasVibrator; @@ -205,7 +218,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; private final ControlsListingController mControlsListingController; - private boolean mAnyControlsProviders = false; + private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); + private ControlsController mControlsController; + private SharedPreferences mControlsPreferences; + private final RingerModeTracker mRingerModeTracker; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -244,7 +260,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ControlsUiController controlsUiController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, ControlsListingController controlsListingController, - ControlsController controlsController, UiEventLogger uiEventLogger) { + ControlsController controlsController, UiEventLogger uiEventLogger, + RingerModeTracker ringerModeTracker) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -271,6 +288,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mBackgroundExecutor = backgroundExecutor; mControlsListingController = controlsListingController; mBlurUtils = blurUtils; + mRingerModeTracker = ringerModeTracker; + mControlsController = controlsController; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -290,6 +309,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( R.bool.config_useFixedVolume); + if (mShowSilentToggle) { + mRingerModeTracker.getRingerMode().observe(this, ringer -> + mHandler.sendEmptyMessage(MESSAGE_REFRESH) + ); + } mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); mScreenshotHelper = new ScreenshotHelper(context); @@ -309,45 +333,54 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }); - String preferredControlsPackage = mContext.getResources() - .getString(com.android.systemui.R.string.config_controlsPreferredPackage); mControlsListingController.addCallback(list -> { - mAnyControlsProviders = !list.isEmpty(); - - /* - * See if any service providers match the preferred component. If they do, - * and there are no current favorites, and we haven't successfully loaded favorites to - * date, query the preferred component for a limited number of suggested controls. - */ - ComponentName preferredComponent = null; - for (ControlsServiceInfo info : list) { - if (info.componentName.getPackageName().equals(preferredControlsPackage)) { - preferredComponent = info.componentName; - break; - } - } - - if (preferredComponent == null) return; - - SharedPreferences prefs = context.getSharedPreferences(PREFS_CONTROLS_FILE, - Context.MODE_PRIVATE); - boolean isSeeded = prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false); - boolean hasFavorites = controlsController.getFavorites().size() > 0; - if (!isSeeded && !hasFavorites) { - controlsController.seedFavoritesForComponent( - preferredComponent, - (accepted) -> { - Log.i(TAG, "Controls seeded: " + accepted); - prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, - accepted).apply(); - } - ); - } + mControlsServiceInfos = list; }); + + // Need to be user-specific with the context to make sure we read the correct prefs + Context userContext = context.createContextAsUser( + new UserHandle(mUserManager.getUserHandle()), 0); + mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE, + Context.MODE_PRIVATE); + } + private void seedFavorites() { + if (mControlsServiceInfos.isEmpty() + || mControlsController.getFavorites().size() > 0 + || mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) { + return; + } + + /* + * See if any service providers match the preferred component. If they do, + * and there are no current favorites, and we haven't successfully loaded favorites to + * date, query the preferred component for a limited number of suggested controls. + */ + String preferredControlsPackage = mContext.getResources() + .getString(com.android.systemui.R.string.config_controlsPreferredPackage); + + ComponentName preferredComponent = null; + for (ControlsServiceInfo info : mControlsServiceInfos) { + if (info.componentName.getPackageName().equals(preferredControlsPackage)) { + preferredComponent = info.componentName; + break; + } + } + if (preferredComponent == null) { + Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed"); + mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply(); + } + mControlsController.seedFavoritesForComponent( + preferredComponent, + (accepted) -> { + Log.i(TAG, "Controls seeded: " + accepted); + mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, + accepted).apply(); + }); + } /** * Show the global actions dialog (creating if necessary) @@ -393,6 +426,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, awakenIfNecessary(); mDialog = createDialog(); prepareDialog(); + seedFavorites(); // If we only have 1 item and it's a simple press action, just do this action. if (mAdapter.getCount() == 1 @@ -489,6 +523,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mAdapter = new MyAdapter(); + mDepthController.setShowingHomeControls(shouldShowControls()); ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(), mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, @@ -593,7 +628,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean shouldBeSeparated() { - return true; + return !shouldShowControls(); } @Override @@ -601,7 +636,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { View v = super.create(context, convertView, parent, inflater); int textColor; - if (shouldBeSeparated()) { + if (shouldShowControls()) { + v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor( + com.android.systemui.R.color.global_actions_emergency_background))); + textColor = v.getResources().getColor( + com.android.systemui.R.color.global_actions_emergency_text); + } else if (shouldBeSeparated()) { textColor = v.getResources().getColor( com.android.systemui.R.color.global_actions_alert_text); } else { @@ -611,7 +651,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, TextView messageView = v.findViewById(R.id.message); messageView.setTextColor(textColor); messageView.setSelected(true); // necessary for marquee to work - ImageView icon = (ImageView) v.findViewById(R.id.icon); + ImageView icon = v.findViewById(R.id.icon); icon.getDrawable().setTint(textColor); return v; } @@ -981,18 +1021,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, refreshSilentMode(); mAirplaneModeOn.updateState(mAirplaneState); mAdapter.notifyDataSetChanged(); - if (mShowSilentToggle) { - IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); - mBroadcastDispatcher.registerReceiver(mRingerModeReceiver, filter); - } + mLifecycle.setCurrentState(Lifecycle.State.RESUMED); } private void refreshSilentMode() { if (!mHasVibrator) { - final boolean silentModeOn = - mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + Integer value = mRingerModeTracker.getRingerMode().getValue(); + final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL; ((ToggleAction) mSilentModeAction).updateState( - silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + silentModeOn ? ToggleState.On : ToggleState.Off); } } @@ -1004,14 +1041,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mDialog = null; } mWindowManagerFuncs.onGlobalActionsHidden(); - if (mShowSilentToggle) { - try { - mBroadcastDispatcher.unregisterReceiver(mRingerModeReceiver); - } catch (IllegalArgumentException ie) { - // ignore this - Log.w(TAG, ie); - } - } + mLifecycle.setCurrentState(Lifecycle.State.DESTROYED); } /** @@ -1022,6 +1052,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN); } + private int getActionLayoutId() { + if (shouldShowControls()) { + return com.android.systemui.R.layout.global_actions_grid_item_v2; + } + return com.android.systemui.R.layout.global_actions_grid_item; + } + /** * The adapter used for the list within the global actions dialog, taking into account whether * the keyguard is showing via @@ -1233,20 +1270,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - protected int getActionLayoutId(Context context) { - if (shouldShowControls()) { - return com.android.systemui.R.layout.global_actions_grid_item_v2; - } - return com.android.systemui.R.layout.global_actions_grid_item; - } - public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(getActionLayoutId(context), parent, - false); + View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */); - ImageView icon = (ImageView) v.findViewById(R.id.icon); - TextView messageView = (TextView) v.findViewById(R.id.message); + ImageView icon = v.findViewById(R.id.icon); + TextView messageView = v.findViewById(R.id.message); messageView.setSelected(true); // necessary for marquee to work if (mIcon != null) { @@ -1265,30 +1294,30 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - /** - * A toggle action knows whether it is on or off, and displays an icon and status message - * accordingly. - */ - private static abstract class ToggleAction implements Action { - - enum State { - Off(false), - TurningOn(true), - TurningOff(true), - On(false); + private enum ToggleState { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); - private final boolean inTransition; + private final boolean mInTransition; - State(boolean intermediate) { - inTransition = intermediate; - } + ToggleState(boolean intermediate) { + mInTransition = intermediate; + } - public boolean inTransition() { - return inTransition; - } + public boolean inTransition() { + return mInTransition; } + } - protected State mState = State.Off; + /** + * A toggle action knows whether it is on or off, and displays an icon and status message + * accordingly. + */ + private abstract class ToggleAction implements Action { + + protected ToggleState mState = ToggleState.Off; // prefs protected int mEnabledIconResId; @@ -1332,13 +1361,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, LayoutInflater inflater) { willCreate(); - View v = inflater.inflate(com.android.systemui.R - .layout.global_actions_grid_item, parent, false); + View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */); ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); final boolean enabled = isEnabled(); - boolean on = ((mState == State.On) || (mState == State.TurningOn)); + boolean on = ((mState == ToggleState.On) || (mState == ToggleState.TurningOn)); if (messageView != null) { messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); @@ -1363,7 +1391,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } - final boolean nowOn = !(mState == State.On); + final boolean nowOn = !(mState == ToggleState.On); onToggle(nowOn); changeStateFromPress(nowOn); } @@ -1380,12 +1408,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * @param buttonOn Whether the button was turned on or off */ protected void changeStateFromPress(boolean buttonOn) { - mState = buttonOn ? State.On : State.Off; + mState = buttonOn ? ToggleState.On : ToggleState.Off; } abstract void onToggle(boolean on); - public void updateState(State state) { + public void updateState(ToggleState state) { mState = state; } } @@ -1419,7 +1447,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // In ECM mode airplane state cannot be changed if (!TelephonyProperties.in_ecm_mode().orElse(false)) { - mState = buttonOn ? State.TurningOn : State.TurningOff; + mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff; mAirplaneState = mState; } } @@ -1554,21 +1582,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, public void onServiceStateChanged(ServiceState serviceState) { if (!mHasTelephony) return; final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; - mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off; mAirplaneModeOn.updateState(mAirplaneState); mAdapter.notifyDataSetChanged(); } }; - private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mHandler.sendEmptyMessage(MESSAGE_REFRESH); - } - } - }; - private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -1613,7 +1632,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off; mAirplaneModeOn.updateState(mAirplaneState); } @@ -1630,10 +1649,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, intent.putExtra("state", on); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); if (!mHasTelephony) { - mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneState = on ? ToggleState.On : ToggleState.Off; } } + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycle; + } + private static final class ActionsDialog extends Dialog implements DialogInterface, ColorExtractor.OnColorsChangedListener { @@ -1780,8 +1805,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } if (mBackgroundDrawable == null) { mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = mBlurUtils.supportsBlursOnWindows() - ? ScrimController.BLUR_SCRIM_ALPHA : ScrimController.BUSY_SCRIM_ALPHA; + if (mControlsUiController != null) { + mScrimAlpha = 1.0f; + } else { + mScrimAlpha = mBlurUtils.supportsBlursOnWindows() + ? ScrimController.BLUR_SCRIM_ALPHA : ScrimController.BUSY_SCRIM_ALPHA; + } } getWindow().setBackgroundDrawable(mBackgroundDrawable); } @@ -1841,8 +1870,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE - : Color.BLACK, animate); + boolean hasControls = mControlsUiController != null; + ((ScrimDrawable) mBackgroundDrawable).setColor( + !hasControls && colors.supportsDarkText() ? Color.WHITE : Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | @@ -1882,6 +1912,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mGlobalActionsLayout); }) .start(); + ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); + root.setOnApplyWindowInsetsListener((v, windowInsets) -> { + if (mControlsUiController != null) { + Insets insets = windowInsets.getInsets(WindowInsets.Type.all()); + root.setPadding(insets.left, insets.top, insets.right, insets.bottom); + } + return WindowInsets.CONSUMED; + }); if (mControlsUiController != null) { mControlsUiController.show(mControlsView); } @@ -2011,6 +2049,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean shouldShowControls() { return mKeyguardStateController.isUnlocked() && mControlsUiController.getAvailable() - && mAnyControlsProviders; + && !mControlsServiceInfos.isEmpty(); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java new file mode 100644 index 000000000000..71bc7c20c026 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media; + +import android.content.Context; +import android.media.session.MediaController; +import android.media.session.MediaSession; + +import javax.inject.Inject; + +/** + * Testable wrapper around {@link MediaController} constructor. + */ +public class MediaControllerFactory { + + private final Context mContext; + + @Inject + public MediaControllerFactory(Context context) { + mContext = context; + } + + /** + * Creates a new MediaController from a session's token. + * + * @param token The token for the session. This value must never be null. + */ + public MediaController create(MediaSession.Token token) { + return new MediaController(mContext, token); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index aa5ebaa22f2d..b7658a9f178d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -46,19 +46,6 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { - if (data.enabled && seekBarView.visibility == View.GONE) { - seekBarView.visibility = View.VISIBLE - elapsedTimeView.visibility = View.VISIBLE - totalTimeView.visibility = View.VISIBLE - } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) { - seekBarView.visibility = View.GONE - elapsedTimeView.visibility = View.GONE - totalTimeView.visibility = View.GONE - return - } - - // TODO: update the style of the disabled progress bar - seekBarView.setEnabled(data.seekAvailable) data.color?.let { var tintList = ColorStateList.valueOf(it) @@ -71,6 +58,17 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { totalTimeView.setTextColor(it) } + if (!data.enabled) { + seekBarView.setEnabled(false) + seekBarView.getThumb().setAlpha(0) + elapsedTimeView.setText("") + totalTimeView.setText("") + return + } + + seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) + seekBarView.setEnabled(data.seekAvailable) + data.elapsedTime?.let { seekBarView.setProgress(it) elapsedTimeView.setText(DateUtils.formatElapsedTime( diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 1868536dca98..e24d29f1aedf 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -231,7 +231,12 @@ public class PipBoundsHandler { /** * @return {@link Rect} of the destination PiP window bounds. */ - Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) { + Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, + Size minimalSize) { + if (!componentName.equals(mLastPipComponentName)) { + onResetReentryBoundsUnchecked(); + mLastPipComponentName = componentName; + } final Rect destinationBounds; if (bounds == null) { final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize); @@ -246,11 +251,7 @@ public class PipBoundsHandler { transformBoundsToAspectRatio(destinationBounds, aspectRatio, false /* useCurrentMinEdgeSize */); } - if (destinationBounds.equals(bounds)) { - return bounds; - } mAspectRatio = aspectRatio; - onResetReentryBoundsUnchecked(); mLastDestinationBounds.set(destinationBounds); return destinationBounds; } @@ -483,6 +484,7 @@ public class PipBoundsHandler { pw.println(prefix + TAG); pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName); pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction); + pw.println(innerPrefix + "mReentrySize=" + mReentrySize); pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo); pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio); pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 8cff20ac31f7..d2994aebeb33 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -103,6 +103,7 @@ public class PipTaskOrganizer extends TaskOrganizer { @Override public void onPipAnimationEnd(SurfaceControl.Transaction tx, PipAnimationController.PipTransitionAnimator animator) { + finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection()); mMainHandler.post(() -> { for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); @@ -110,7 +111,6 @@ public class PipTaskOrganizer extends TaskOrganizer { animator.getTransitionDirection()); } }); - finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection()); } @Override @@ -247,7 +247,7 @@ public class PipTaskOrganizer extends TaskOrganizer { public void onTaskAppeared(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(info, "Requires RunningTaskInfo"); final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(info.pictureInPictureParams), + info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */, getMinimalSize(info.topActivityInfo)); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); mTaskInfo = info; @@ -303,7 +303,7 @@ public class PipTaskOrganizer extends TaskOrganizer { return; } final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(newParams), + info.topActivity, getAspectRatioOrDefault(newParams), null /* bounds */, getMinimalSize(info.topActivityInfo)); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, @@ -319,23 +319,29 @@ public class PipTaskOrganizer extends TaskOrganizer { * TODO(b/152809058): consolidate the display info handling logic in SysUI */ @SuppressWarnings("unchecked") - public void mayUpdateCurrentAnimationOnRotationChange() { + public void onMovementBoundsChanged(boolean fromImeAdjustment, boolean fromShelfAdjustment) { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); - if (animator != null && animator.isRunning() - && animator.getTransitionDirection() == TRANSITION_DIRECTION_TO_PIP) { - final Rect currentDestinationBounds = animator.getDestinationBounds(); - if (mPipBoundsHandler.getDisplayBounds().contains(currentDestinationBounds)) { - return; - } - final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams), - null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo)); - if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) { - animator.updateEndValue(newDestinationBounds); - } - animator.setDestinationBounds(newDestinationBounds); + if (animator == null || !animator.isRunning() + || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) { + return; + } + + final Rect currentDestinationBounds = animator.getDestinationBounds(); + if (!fromImeAdjustment && !fromShelfAdjustment + && mPipBoundsHandler.getDisplayBounds().contains(currentDestinationBounds)) { + // no need to update the destination bounds, bail early + return; + } + + final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds( + mTaskInfo.topActivity, getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams), + null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo)); + if (newDestinationBounds.equals(currentDestinationBounds)) return; + if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) { + animator.updateEndValue(newDestinationBounds); } + animator.setDestinationBounds(newDestinationBounds); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java deleted file mode 100644 index b7258117c48c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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.systemui.pip.phone; - -import android.content.Context; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.widget.FrameLayout; - -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.shared.system.WindowManagerWrapper; - -/** - * Displays the dismiss UI and target for floating objects. - */ -public class PipDismissViewController { - - // This delay controls how long to wait before we show the target when the user first moves - // the PIP, to prevent the target from animating if the user just wants to fling the PIP - public static final int SHOW_TARGET_DELAY = 100; - private static final int SHOW_TARGET_DURATION = 350; - private static final int HIDE_TARGET_DURATION = 225; - - private Context mContext; - private WindowManager mWindowManager; - private View mDismissView; - - // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss - private View mTargetView; - private int mTargetSlop; - private Point mWindowSize; - private int[] mLoc = new int[2]; - private boolean mIntersecting; - private Vibrator mVibe; - - public PipDismissViewController(Context context) { - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - } - - /** - * Creates the dismiss target for showing via {@link #showDismissTarget()}. - */ - public void createDismissTarget() { - if (mDismissView == null) { - // Determine sizes for the view - final Rect stableInsets = new Rect(); - WindowManagerWrapper.getInstance().getStableInsets(stableInsets); - mWindowSize = new Point(); - mWindowManager.getDefaultDisplay().getRealSize(mWindowSize); - final int gradientHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.pip_dismiss_gradient_height); - final int bottomMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.pip_dismiss_text_bottom_margin); - mTargetSlop = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_dismiss_slop); - - // Create a new view for the dismiss target - LayoutInflater inflater = LayoutInflater.from(mContext); - mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null); - mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mDismissView.forceHasOverlappingRendering(false); - - // Set the gradient background - Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim); - gradient.setAlpha((int) (255 * 0.85f)); - mDismissView.setBackground(gradient); - - // Adjust bottom margins of the text - mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text); - FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams(); - tlp.bottomMargin = stableInsets.bottom + bottomMargin; - mTargetView.setLayoutParams(tlp); - - // Add the target to the window - LayoutParams lp = new LayoutParams( - LayoutParams.MATCH_PARENT, gradientHeight, - 0, mWindowSize.y - gradientHeight, - LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - LayoutParams.FLAG_LAYOUT_IN_SCREEN - | LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - lp.setTitle("pip-dismiss-overlay"); - lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.setFitInsetsTypes(0 /* types */); - mWindowManager.addView(mDismissView, lp); - } - mDismissView.animate().cancel(); - } - - - /** - * Updates the dismiss target based on location of the view, only used for bubbles not for PIP. - * - * @return whether the view is within the dismiss target. - */ - public boolean updateTarget(View view) { - if (mDismissView == null) { - return false; - } - if (mDismissView.getAlpha() > 0) { - view.getLocationOnScreen(mLoc); - Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(), - mLoc[1] + view.getHeight()); - mTargetView.getLocationOnScreen(mLoc); - Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(), - mLoc[1] + mTargetView.getHeight()); - expandRect(targetRect, mTargetSlop); - boolean intersecting = targetRect.intersect(viewRect); - if (intersecting != mIntersecting) { - // TODO: is this the right effect? - mVibe.vibrate(VibrationEffect.get(intersecting - ? VibrationEffect.EFFECT_CLICK - : VibrationEffect.EFFECT_TICK)); - } - mIntersecting = intersecting; - return intersecting; - } - return false; - } - - /** - * Shows the dismiss target. - */ - public void showDismissTarget() { - mDismissView.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR) - .setStartDelay(SHOW_TARGET_DELAY) - .setDuration(SHOW_TARGET_DURATION) - .start(); - } - - /** - * Hides and destroys the dismiss target. - */ - public void destroyDismissTarget() { - if (mDismissView != null) { - mDismissView.animate() - .alpha(0f) - .setInterpolator(Interpolators.LINEAR) - .setStartDelay(0) - .setDuration(HIDE_TARGET_DURATION) - .withEndAction(new Runnable() { - @Override - public void run() { - mWindowManager.removeViewImmediate(mDismissView); - mDismissView = null; - } - }) - .start(); - } - } - - private void expandRect(Rect outRect, int expandAmount) { - outRect.left = Math.max(0, outRect.left - expandAmount); - outRect.top = Math.max(0, outRect.top - expandAmount); - outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount); - outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 99d6df517224..918c45b52d61 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -362,7 +362,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds, animatingBounds, fromImeAdjustment, fromShelfAdjustment, mTmpDisplayInfo.rotation); - mPipTaskOrganizer.mayUpdateCurrentAnimationOnRotationChange(); + mPipTaskOrganizer.onMovementBoundsChanged(fromImeAdjustment, fromShelfAdjustment); } public void dump(PrintWriter pw) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 8397c65dbdb0..a8a5d896537f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -16,16 +16,12 @@ package com.android.systemui.pip.phone; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager.StackInfo; import android.app.IActivityTaskManager; import android.content.Context; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; import android.os.RemoteException; @@ -40,6 +36,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.animation.FloatProperties; import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; import java.util.function.Consumer; @@ -61,9 +58,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Friction to use for PIP when it moves via physics fling animations. */ private static final float DEFAULT_FRICTION = 2f; - // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP - private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f; - private final Context mContext; private final IActivityTaskManager mActivityTaskManager; private final PipTaskOrganizer mPipTaskOrganizer; @@ -103,7 +97,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Update listener that resizes the PIP to {@link #mAnimatedBounds}. */ - private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = + final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = (target, values) -> resizePipUnchecked(mAnimatedBounds); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ @@ -122,6 +116,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set; + /** + * Whether we're springing to the touch event location (vs. moving it to that position + * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was + * 'stuck' in the target and needs to catch up to the touch location. + */ + private boolean mSpringingToTouch = false; + public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager, PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils, @@ -166,15 +167,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, */ void synchronizePinnedStackBounds() { cancelAnimations(); - try { - StackInfo stackInfo = mActivityTaskManager.getStackInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (stackInfo != null) { - mBounds.set(stackInfo.bounds); - } - } catch (RemoteException e) { - Log.w(TAG, "Failed to get pinned stack bounds"); - } + mBounds.set(mPipTaskOrganizer.getLastReportedBounds()); } /** @@ -211,9 +204,35 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mFloatingContentCoordinator.onContentMoved(this); } - cancelAnimations(); - resizePipUnchecked(toBounds); - mBounds.set(toBounds); + if (!mSpringingToTouch) { + // If we are moving PIP directly to the touch event locations, cancel any animations and + // move PIP to the given bounds. + cancelAnimations(); + resizePipUnchecked(toBounds); + mBounds.set(toBounds); + } else { + // If PIP is 'catching up' after being stuck in the dismiss target, update the animation + // to spring towards the new touch location. + mAnimatedBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig) + .withEndActions(() -> mSpringingToTouch = false); + + startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */); + } + } + + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + if (springingToTouch) { + mAnimatedBounds.set(mBounds); + } + + mSpringingToTouch = springingToTouch; + } + + void prepareForAnimation() { + mAnimatedBounds.set(mBounds); } /** @@ -278,24 +297,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @return whether the PiP at the current bounds should be dismissed. - */ - boolean shouldDismissPip() { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - final int y = displaySize.y - mStableInsets.bottom; - if (mBounds.bottom > y) { - float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height(); - return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION; - } - return false; - } - - /** * Flings the PiP to the closest snap target. */ void flingToSnapTarget( - float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) { + float velocityX, float velocityY, + @Nullable Runnable updateAction, @Nullable Runnable endAction) { mAnimatedBounds.set(mBounds); mAnimatedBoundsPhysicsAnimator .flingThenSpring( @@ -303,9 +309,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, true /* flingMustReachMinOrMax */) .flingThenSpring( FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig) - .addUpdateListener((target, values) -> updateAction.run()) .withEndActions(endAction); + if (updateAction != null) { + mAnimatedBoundsPhysicsAnimator.addUpdateListener( + (target, values) -> updateAction.run()); + } + final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; final float estimatedFlingYEndValue = PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); @@ -338,16 +348,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Animates the dismissal of the PiP off the edge of the screen. */ void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) { - final float velocity = PointF.length(velocityX, velocityY); - final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); - final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling); - mAnimatedBounds.set(mBounds); - // Animate to the dismiss end point, and then dismiss PIP. + // Animate off the bottom of the screen, then dismiss PIP. mAnimatedBoundsPhysicsAnimator - .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig) - .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig) + .spring(FloatProperties.RECT_Y, + mBounds.bottom + mBounds.height(), + velocityY, + mSpringConfig) .withEndActions(this::dismissPip); // If we were provided with an update action, run it whenever there's an update. @@ -356,7 +364,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (target, values) -> updateAction.run()); } - startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */); + startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */); } /** @@ -408,6 +416,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); mAnimatingToBounds.setEmpty(); + mSpringingToTouch = false; } /** Set new fling configs whose min/max values respect the given movement bounds. */ @@ -426,7 +435,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * the 'real' bounds to equal the final animated bounds. */ private void startBoundsAnimator(float toX, float toY) { - cancelAnimations(); + if (!mSpringingToTouch) { + cancelAnimations(); + } // Set animatingToBounds directly to avoid allocating a new Rect, but then call // setAnimatingToBounds to run the normal logic for changing animatingToBounds. @@ -484,47 +495,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @return the coordinates the PIP should animate to based on the direction of velocity when - * dismissing. + * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the + * magnetic dismiss target so it can calculate PIP's size and position. */ - private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - final float bottomBound = displaySize.y + pipBounds.height() * .1f; - if (isFling && velX != 0 && velY != 0) { - // Line is defined by: y = mx + b, m = slope, b = y-intercept - // Find the slope - final float slope = velY / velX; - // Sub in slope and PiP position to solve for y-intercept: b = y - mx - final float yIntercept = pipBounds.top - slope * pipBounds.left; - // Now find the point on this line when y = bottom bound: x = (y - b) / m - final float x = (bottomBound - yIntercept) / slope; - return new Point((int) x, (int) bottomBound); - } else { - // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement, - // just animate downwards. - return new Point(pipBounds.left, (int) bottomBound); - } - } + MagnetizedObject<Rect> getMagnetizedPip() { + return new MagnetizedObject<Rect>( + mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } - /** - * @return whether the gesture it towards the dismiss area based on the velocity when - * dismissing. - */ - public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY, - boolean isFling) { - Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling); - // Center the point - endpoint.x += pipBounds.width() / 2; - endpoint.y += pipBounds.height() / 2; - - // The dismiss area is the middle third of the screen, half the PIP's height from the bottom - Point size = new Point(); - mContext.getDisplay().getRealSize(size); - final int left = size.x / 3; - Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2, - size.y + pipBounds.height()); - return dismissArea.contains(endpoint.x, endpoint.y); + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; } public void dump(PrintWriter pw, String prefix) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 7cc2759ad59a..1e9daab4a0b6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -20,11 +20,13 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; +import android.annotation.SuppressLint; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -33,14 +35,23 @@ import android.os.RemoteException; import android.util.Log; import android.util.Pair; import android.util.Size; +import android.view.Gravity; import android.view.IPinnedStackController; import android.view.InputEvent; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; @@ -51,7 +62,10 @@ import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; +import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -62,9 +76,6 @@ import java.io.PrintWriter; public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; - // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed. - private static final boolean ENABLE_FLING_DISMISS = false; - private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225; private static final int BOTTOM_OFFSET_BUFFER_DP = 1; @@ -73,17 +84,45 @@ public class PipTouchHandler { // Allow PIP to resize to a slightly bigger state upon touch private final boolean mEnableResize; private final Context mContext; + private final WindowManager mWindowManager; private final IActivityManager mActivityManager; private final PipBoundsHandler mPipBoundsHandler; private PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; - private final PipDismissViewController mDismissViewController; private final PipSnapAlgorithm mSnapAlgorithm; private final AccessibilityManager mAccessibilityManager; private boolean mShowPipMenuOnAnimationEnd = false; + /** + * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move + * PIP. + */ + private MagnetizedObject<Rect> mMagnetizedPip; + + /** + * Container for the dismiss circle, so that it can be animated within the container via + * translation rather than within the WindowManager via slow layout animations. + */ + private ViewGroup mTargetViewContainer; + + /** Circle view used to render the dismiss target. */ + private DismissCircleView mTargetView; + + /** + * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius. + */ + private MagnetizedObject.MagneticTarget mMagneticTarget; + + /** PhysicsAnimator instance for animating the dismiss target in/out. */ + private PhysicsAnimator<View> mMagneticTargetAnimator; + + /** Default configuration to use for springing the dismiss target in/out. */ + private final PhysicsAnimator.SpringConfig mTargetSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY); + // The current movement bounds private Rect mMovementBounds = new Rect(); // The current resized bounds, changed by user resize. @@ -104,21 +143,20 @@ public class PipTouchHandler { private int mDeferResizeToNormalBoundsUntilRotation = -1; private int mDisplayRotation; + /** + * Runnable that can be posted delayed to show the target. This needs to be saved as a member + * variable so we can pass it to removeCallbacks. + */ + private Runnable mShowTargetAction = this::showDismissTargetMaybe; + private Handler mHandler = new Handler(); - private Runnable mShowDismissAffordance = new Runnable() { - @Override - public void run() { - if (mEnableDismissDragToEdge) { - mDismissViewController.showDismissTarget(); - } - } - }; // Behaviour states private int mMenuState = MENU_STATE_NONE; private boolean mIsImeShowing; private int mImeHeight; private int mImeOffset; + private int mDismissAreaHeight; private boolean mIsShelfShowing; private int mShelfHeight; private int mMovementBoundsExtraOffsets; @@ -168,6 +206,7 @@ public class PipTouchHandler { } } + @SuppressLint("InflateParams") public PipTouchHandler(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, InputConsumerController inputConsumerController, @@ -180,9 +219,9 @@ public class PipTouchHandler { mContext = context; mActivityManager = activityManager; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mMenuController = menuController; mMenuController.addListener(new PipMenuListener()); - mDismissViewController = new PipDismissViewController(context); mSnapAlgorithm = pipSnapAlgorithm; mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(), 2.5f); @@ -200,6 +239,7 @@ public class PipTouchHandler { mExpandedShortestEdgeSize = res.getDimensionPixelSize( R.dimen.pip_expanded_shortest_edge_size); mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); + mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); @@ -212,6 +252,56 @@ public class PipTouchHandler { mFloatingContentCoordinator = floatingContentCoordinator; mConnection = new PipAccessibilityInteractionConnection(mMotionHelper, this::onAccessibilityShowMenu, mHandler); + + final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); + mTargetView = new DismissCircleView(context); + final FrameLayout.LayoutParams newParams = + new FrameLayout.LayoutParams(targetSize, targetSize); + newParams.gravity = Gravity.CENTER; + mTargetView.setLayoutParams(newParams); + + mTargetViewContainer = new FrameLayout(context); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTargetView); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMotionHelper.prepareForAnimation(); + + // Show the dismiss target, in case the initial touch event occurred within the + // magnetic field radius. + showDismissTargetMaybe(); + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null, null); + hideDismissTarget(); + } else { + mMotionHelper.setSpringingToTouch(true); + } + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mHandler.post(() -> { + mMotionHelper.animateDismiss(0, 0, null); + hideDismissTarget(); + }); + + + MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, + PipUtils.getTopPipActivity(mContext, mActivityManager)); + } + }); + + mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); } public void setTouchGesture(PipTouchGesture gesture) { @@ -231,7 +321,8 @@ public class PipTouchHandler { } public void onActivityPinned() { - cleanUpDismissTarget(); + createDismissTargetMaybe(); + mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); mFloatingContentCoordinator.onContentAdded(mMotionHelper); @@ -264,6 +355,10 @@ public class PipTouchHandler { public void onConfigurationChanged() { mMotionHelper.onConfigurationChanged(); mMotionHelper.synchronizePinnedStackBounds(); + + // Recreate the dismiss target for the new orientation. + cleanUpDismissTarget(); + createDismissTargetMaybe(); } public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { @@ -351,6 +446,74 @@ public class PipTouchHandler { } } + /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ + private void createDismissTargetMaybe() { + if (!mTargetViewContainer.isAttachedToWindow()) { + mHandler.removeCallbacks(mShowTargetAction); + mMagneticTargetAnimator.cancel(); + + final Point windowSize = new Point(); + mWindowManager.getDefaultDisplay().getRealSize(windowSize); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + mDismissAreaHeight, + 0, windowSize.y - mDismissAreaHeight, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + lp.setTitle("pip-dismiss-overlay"); + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setFitInsetsTypes(0 /* types */); + + mTargetViewContainer.setVisibility(View.INVISIBLE); + mWindowManager.addView(mTargetViewContainer, lp); + } + } + + /** Makes the dismiss target visible and animates it in, if it isn't already visible. */ + private void showDismissTargetMaybe() { + createDismissTargetMaybe(); + + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + + mTargetView.setTranslationY(mTargetViewContainer.getHeight()); + mTargetViewContainer.setVisibility(View.VISIBLE); + + // Set the magnetic field radius to half of PIP's width. + mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width()); + + // Cancel in case we were in the middle of animating it out. + mMagneticTargetAnimator.cancel(); + mMagneticTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig) + .start(); + } + } + + /** Animates the magnetic dismiss target out and then sets it to GONE. */ + private void hideDismissTarget() { + mHandler.removeCallbacks(mShowTargetAction); + mMagneticTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, + mTargetViewContainer.getHeight(), + mTargetSpringConfig) + .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE)) + .start(); + } + + /** + * Removes the dismiss target and cancels any pending callbacks to show it. + */ + private void cleanUpDismissTarget() { + mHandler.removeCallbacks(mShowTargetAction); + + if (mTargetViewContainer.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(mTargetViewContainer); + } + } + private void onRegistrationChanged(boolean isRegistered) { mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered ? mConnection : null); @@ -375,8 +538,24 @@ public class PipTouchHandler { if (mPinnedStackController == null) { return true; } + MotionEvent ev = (MotionEvent) inputEvent; + if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) { + // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event + // to the touch state. Touch state needs a DOWN event in order to later process MOVE + // events it'll receive if the object is dragged out of the magnetic field. + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mTouchState.onTouchEvent(ev); + } + + // Continue tracking velocity when the object is in the magnetic field, since we want to + // respect touch input velocity if the object is dragged out and then flung. + mTouchState.addMovementToVelocityTracker(ev); + + return true; + } + // Update the touch state mTouchState.onTouchEvent(ev); @@ -413,8 +592,14 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_ENTER: - mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), - mMovementBounds, false /* allowMenuTimeout */, false /* willResizeMenu */); + // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably + // on and changing MotionEvents into HoverEvents. + // Let's not enable menu show/hide for a11y services. + if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), + mMovementBounds, false /* allowMenuTimeout */, + false /* willResizeMenu */); + } case MotionEvent.ACTION_HOVER_MOVE: { if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) { sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); @@ -423,7 +608,12 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_EXIT: { - mMenuController.hideMenu(); + // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably + // on and changing MotionEvents into HoverEvents. + // Let's not enable menu show/hide for a11y services. + if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mMenuController.hideMenu(); + } if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) { sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); mSendingHoverAccessibilityEvents = false; @@ -600,17 +790,13 @@ public class PipTouchHandler { mDelta.set(0f, 0f); mStartPosition.set(bounds.left, bounds.top); mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; + mMotionHelper.setSpringingToTouch(false); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it if (mMenuState != MENU_STATE_NONE) { mMenuController.pokeMenu(); } - - if (mEnableDismissDragToEdge) { - mDismissViewController.createDismissTarget(); - mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); - } } @Override @@ -623,8 +809,10 @@ public class PipTouchHandler { mSavedSnapFraction = -1f; if (mEnableDismissDragToEdge) { - mHandler.removeCallbacks(mShowDismissAffordance); - mDismissViewController.showDismissTarget(); + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + mHandler.removeCallbacks(mShowTargetAction); + showDismissTargetMaybe(); + } } } @@ -644,10 +832,6 @@ public class PipTouchHandler { mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds, true /* isDragging */); - if (mEnableDismissDragToEdge) { - updateDismissFraction(); - } - final PointF curPos = touchState.getLastTouchPosition(); if (mMovementWithinDismiss) { // Track if movement remains near the bottom edge to identify swipe to dismiss @@ -661,9 +845,7 @@ public class PipTouchHandler { @Override public boolean onUp(PipTouchState touchState) { if (mEnableDismissDragToEdge) { - // Clean up the dismiss target regardless of the touch state in case the touch - // enabled state changes while the user is interacting - cleanUpDismissTarget(); + hideDismissTarget(); } if (!touchState.isUserInteracting()) { @@ -671,26 +853,8 @@ public class PipTouchHandler { } final PointF vel = touchState.getVelocity(); - final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y); final float velocity = PointF.length(vel.x, vel.y); final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); - final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS - && touchState.getLastTouchPosition().y >= mMovementBounds.bottom - && mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x, - vel.y, isFling); - final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal - && (mMovementWithinDismiss || isUpWithinDimiss); - if (mEnableDismissDragToEdge) { - // Check if the user dragged or flung the PiP offscreen to dismiss it - if (mMotionHelper.shouldDismissPip() || isFlingToBot) { - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, - PipUtils.getTopPipActivity(mContext, mActivityManager)); - mMotionHelper.animateDismiss( - vel.x, vel.y, - PipTouchHandler.this::updateDismissFraction /* updateAction */); - return true; - } - } if (touchState.isDragging()) { Runnable endAction = null; @@ -749,14 +913,6 @@ public class PipTouchHandler { } /** - * Removes the dismiss target and cancels any pending callbacks to show it. - */ - private void cleanUpDismissTarget() { - mHandler.removeCallbacks(mShowDismissAffordance); - mDismissViewController.destroyDismissTarget(); - } - - /** * @return whether the menu will resize as a part of showing the full menu. */ private boolean willResizeMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java index e3f65ef812fb..dc286c1c2de5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java @@ -92,7 +92,7 @@ public class PipTouchState { // Initialize the velocity tracker initOrResetVelocityTracker(); - addMovement(ev); + addMovementToVelocityTracker(ev); mActivePointerId = ev.getPointerId(0); if (DEBUG) { @@ -120,7 +120,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId); @@ -151,7 +151,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); int pointerIndex = ev.getActionIndex(); int pointerId = ev.getPointerId(pointerIndex); @@ -174,7 +174,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); mVelocityTracker.computeCurrentVelocity(1000, mViewConfig.getScaledMaximumFlingVelocity()); mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); @@ -318,6 +318,20 @@ public class PipTouchState { return -1; } + void addMovementToVelocityTracker(MotionEvent event) { + if (mVelocityTracker == null) { + return; + } + + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + private void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -333,16 +347,6 @@ public class PipTouchState { } } - private void addMovement(MotionEvent event) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - float deltaX = event.getRawX() - event.getX(); - float deltaY = event.getRawY() - event.getY(); - event.offsetLocation(deltaX, deltaY); - mVelocityTracker.addMovement(event); - event.offsetLocation(-deltaX, -deltaY); - } - public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java index 9c175bc2b756..8efeef1ffa0a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java @@ -30,6 +30,14 @@ import com.android.systemui.R; */ public class PipControlsView extends LinearLayout { + public PipControlsView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService( diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 52c8960d1ccf..e25bfaab93a9 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -239,6 +239,12 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mInitialized = true; mContext = context; mPipBoundsHandler = pipBoundsHandler; + // Ensure that we have the display info in case we get calls to update the bounds before the + // listener calls back + final DisplayInfo displayInfo = new DisplayInfo(); + context.getDisplay().getDisplayInfo(displayInfo); + mPipBoundsHandler.onDisplayInfoChanged(displayInfo); + mResizeAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler, @@ -289,7 +295,6 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); - mPipTaskOrganizer.registerPipTransitionCallback(this); try { WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener); @@ -334,6 +339,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * Shows the picture-in-picture menu if an activity is in picture-in-picture mode. */ public void showPictureInPictureMenu() { + if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription()); + if (getState() == STATE_PIP) { resizePinnedStack(STATE_PIP_MENU); } @@ -343,10 +350,18 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * Closes PIP (PIPed activity and PIP system UI). */ public void closePip() { + if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription()); + closePipInternal(true); } private void closePipInternal(boolean removePipStack) { + if (DEBUG) { + Log.d(TAG, + "closePipInternal() removePipStack=" + removePipStack + ", current state=" + + getStateDescription()); + } + mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; mPipMediaController = null; @@ -371,6 +386,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * Moves the PIPed activity to the fullscreen and closes PIP system UI. */ void movePipToFullscreen() { + if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription()); + mPipTaskId = TASK_ID_NO_PIP; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onMoveToFullscreen(); @@ -386,6 +403,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio public void suspendPipResizing(int reason) { if (DEBUG) Log.d(TAG, "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2)); + mSuspendPipResizingReason |= reason; } @@ -408,7 +426,11 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * @param state In Pip state also used to determine the new size for the Pip. */ void resizePinnedStack(int state) { - if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception()); + if (DEBUG) { + Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state=" + + getStateDescription(), new Exception()); + } + boolean wasStateNoPip = (mState == STATE_NO_PIP); for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onPipResizeAboutToStart(); @@ -418,7 +440,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring" + " mSuspendPipResizingReason=" + mSuspendPipResizingReason + " mResumeResizePinnedStackRunnableState=" - + mResumeResizePinnedStackRunnableState); + + stateToName(mResumeResizePinnedStackRunnableState)); return; } mState = state; @@ -458,7 +480,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}. */ private void showPipMenu() { - if (DEBUG) Log.d(TAG, "showPipMenu()"); + if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription()); + mState = STATE_PIP_MENU; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onShowPipMenu(); @@ -712,6 +735,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio private void onPipTransitionFinishedOrCanceled() { if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()"); + if (getState() == STATE_PIP_MENU) { showPipMenu(); } @@ -753,4 +777,28 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio WindowManagerWrapper.getInstance().setPipVisibility(visible); }); } + + private String getStateDescription() { + if (mSuspendPipResizingReason == 0) { + return stateToName(mState); + } + return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState) + + " is suspended)"; + } + + private static String stateToName(int state) { + switch (state) { + case STATE_NO_PIP: + return "NO_PIP"; + + case STATE_PIP: + return "PIP"; + + case STATE_PIP_MENU: + return "PIP_MENU"; + + default: + return "UNKNOWN(" + state + ")"; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java index f43f8e795fe6..c7e77ccfa488 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java @@ -22,6 +22,7 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.os.Bundle; +import android.util.Log; import com.android.systemui.R; import com.android.systemui.pip.tv.dagger.TvPipComponent; @@ -34,6 +35,7 @@ import javax.inject.Inject; * Activity to show the PIP menu to control PIP. */ public class PipMenuActivity extends Activity implements PipManager.Listener { + private static final boolean DEBUG = false; private static final String TAG = "PipMenuActivity"; static final String EXTRA_CUSTOM_ACTIONS = "custom_actions"; @@ -47,7 +49,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { private boolean mRestorePipSizeWhenClose; private PipControlsViewController mPipControlsViewController; - @Inject public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) { super(); @@ -57,6 +58,8 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onCreate(Bundle bundle) { + if (DEBUG) Log.d(TAG, "onCreate()"); + super.onCreate(bundle); if (!mPipManager.isPipShown()) { finish(); @@ -81,13 +84,18 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onNewIntent(Intent intent) { + if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent); super.onNewIntent(intent); onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS)); } private void restorePipAndFinish() { + if (DEBUG) Log.d(TAG, "restorePipAndFinish()"); + if (mRestorePipSizeWhenClose) { + if (DEBUG) Log.d(TAG, " > restoring to the default position"); + // When PIP menu activity is closed, restore to the default position. mPipManager.resizePinnedStack(PipManager.STATE_PIP); } @@ -96,12 +104,16 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onResume() { + if (DEBUG) Log.d(TAG, "onResume()"); + super.onResume(); mFadeInAnimation.start(); } @Override public void onPause() { + if (DEBUG) Log.d(TAG, "onPause()"); + super.onPause(); mFadeOutAnimation.start(); restorePipAndFinish(); @@ -109,6 +121,8 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy()"); + super.onDestroy(); mPipManager.removeListener(this); mPipManager.resumePipResizing( @@ -117,29 +131,41 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onBackPressed() { + if (DEBUG) Log.d(TAG, "onBackPressed()"); + restorePipAndFinish(); } @Override - public void onPipEntered() { } + public void onPipEntered() { + if (DEBUG) Log.d(TAG, "onPipEntered()"); + } @Override public void onPipActivityClosed() { + if (DEBUG) Log.d(TAG, "onPipActivityClosed()"); + finish(); } @Override public void onPipMenuActionsChanged(ParceledListSlice actions) { + if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()"); + boolean hasCustomActions = actions != null && !actions.getList().isEmpty(); mPipControlsViewController.setActions( hasCustomActions ? actions.getList() : Collections.EMPTY_LIST); } @Override - public void onShowPipMenu() { } + public void onShowPipMenu() { + if (DEBUG) Log.d(TAG, "onShowPipMenu()"); + } @Override public void onMoveToFullscreen() { + if (DEBUG) Log.d(TAG, "onMoveToFullscreen()"); + // Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds. // This conflicts with restoring PIP position, so disable it. mRestorePipSizeWhenClose = false; @@ -148,8 +174,17 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onPipResizeAboutToStart() { + if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()"); + finish(); mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); } + + @Override + public void finish() { + if (DEBUG) Log.d(TAG, "finish()", new RuntimeException()); + + super.finish(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 8fa64d3aaf0c..e6876bd98d21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -66,7 +66,10 @@ public class QuickQSPanel extends QSPanel { private int mMaxTiles; protected QSPanel mFullPanel; private QuickQSMediaPlayer mMediaPlayer; + /** Whether or not the QS media player feature is enabled. */ private boolean mUsingMediaPlayer; + /** Whether or not the QuickQSPanel currently contains a media player. */ + private boolean mHasMediaPlayer; private LinearLayout mHorizontalLinearLayout; // Only used with media @@ -185,8 +188,8 @@ public class QuickQSPanel extends QSPanel { boolean switchTileLayout() { if (!mUsingMediaPlayer) return false; - if (mMediaPlayer.hasMediaSession() - && mHorizontalLinearLayout.getVisibility() == View.GONE) { + mHasMediaPlayer = mMediaPlayer.hasMediaSession(); + if (mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.GONE) { mHorizontalLinearLayout.setVisibility(View.VISIBLE); ((View) mRegularTileLayout).setVisibility(View.GONE); mTileLayout.setListening(false); @@ -198,8 +201,7 @@ public class QuickQSPanel extends QSPanel { if (mHost != null) setTiles(mHost.getTiles()); mTileLayout.setListening(mListening); return true; - } else if (!mMediaPlayer.hasMediaSession() - && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) { + } else if (!mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) { mHorizontalLinearLayout.setVisibility(View.GONE); ((View) mRegularTileLayout).setVisibility(View.VISIBLE); mTileLayout.setListening(false); @@ -215,8 +217,9 @@ public class QuickQSPanel extends QSPanel { return false; } - public boolean hasMediaPlayerSession() { - return mMediaPlayer.hasMediaSession(); + /** Returns true if this panel currently contains a media player. */ + public boolean hasMediaPlayer() { + return mHasMediaPlayer; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 11b625f41737..90dc38f3ad12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -22,10 +22,8 @@ import static com.android.systemui.util.Utils.useQsMediaPlayer; import android.annotation.ColorInt; import android.app.ActivityManager; import android.app.AlarmManager; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; @@ -49,13 +47,16 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; @@ -70,6 +71,7 @@ import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.RingerModeTracker; import java.util.ArrayList; import java.util.List; @@ -86,7 +88,7 @@ import javax.inject.Named; */ public class QuickStatusBarHeader extends RelativeLayout implements View.OnClickListener, NextAlarmController.NextAlarmChangeCallback, - ZenModeController.Callback { + ZenModeController.Callback, LifecycleOwner { private static final String TAG = "QuickStatusBarHeader"; private static final boolean DEBUG = false; @@ -137,15 +139,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements private DateView mDateView; private BatteryMeterView mBatteryRemainingIcon; - private BroadcastDispatcher mBroadcastDispatcher; + // Used for RingerModeTracker + private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); - private final BroadcastReceiver mRingerReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mRingerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); - updateStatusText(); - } - }; private boolean mHasTopCutout = false; private int mRoundedCornerPadding = 0; @@ -154,7 +150,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements NextAlarmController nextAlarmController, ZenModeController zenModeController, StatusBarIconController statusBarIconController, ActivityStarter activityStarter, - CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher) { + CommandQueue commandQueue, RingerModeTracker ringerModeTracker) { super(context, attrs); mAlarmController = nextAlarmController; mZenController = zenModeController; @@ -162,8 +158,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements mActivityStarter = activityStarter; mDualToneHandler = new DualToneHandler( new ContextThemeWrapper(context, R.style.QSHeaderTheme)); - mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; + ringerModeTracker.getRingerModeInternal().observe(this, ringer -> { + mRingerMode = ringer; + updateStatusText(); + }); } @Override @@ -344,7 +343,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements if (mQsDisabled) { lp.height = resources.getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayerSession()) { + } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayer()) { lp.height = Math.max(getMinimumHeight(), resources.getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_total_height_with_media)); @@ -484,12 +483,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements if (listening) { mZenController.addCallback(this); mAlarmController.addCallback(this); - mBroadcastDispatcher.registerReceiver(mRingerReceiver, - new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + mLifecycle.setCurrentState(Lifecycle.State.RESUMED); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); - mBroadcastDispatcher.unregisterReceiver(mRingerReceiver); + mLifecycle.setCurrentState(Lifecycle.State.DESTROYED); } } @@ -586,4 +584,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements lp.rightMargin = sideMargins; } } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycle; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 4f9052709870..447f48b5db94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -18,6 +18,8 @@ package com.android.systemui.qs.tiles; import android.content.Context; import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -29,6 +31,7 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.R; import com.android.systemui.qs.PseudoGridView; import com.android.systemui.statusbar.policy.UserSwitcherController; + /** * Quick settings detail view for user switching. */ @@ -101,6 +104,19 @@ public class UserDetailView extends PseudoGridView { return v; } + private static Drawable getDrawable(Context context, + UserSwitcherController.UserRecord item) { + Drawable icon = getIconDrawable(context, item); + int iconColorRes = item.isCurrent ? R.color.qs_user_switcher_selected_avatar_icon_color + : R.color.qs_user_switcher_avatar_icon_color; + icon.setTint(context.getResources().getColor(iconColorRes, context.getTheme())); + + int bgRes = item.isCurrent ? R.drawable.bg_avatar_selected : R.drawable.qs_bg_avatar; + Drawable bg = context.getDrawable(bgRes); + LayerDrawable drawable = new LayerDrawable(new Drawable[]{bg, icon}); + return drawable; + } + @Override public void onClick(View view) { UserSwitcherController.UserRecord tag = diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 3879c164a84c..2df450604d3b 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -21,6 +21,7 @@ import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -32,10 +33,8 @@ import android.graphics.Region.Op; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.util.AttributeSet; import android.util.Slog; -import android.view.Choreographer; import android.view.Display; import android.view.InsetsState; import android.view.MotionEvent; @@ -57,12 +56,12 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.widget.FrameLayout; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.internal.policy.DockedDividerUtils; -import com.android.internal.view.SurfaceFlingerVsyncChoreographer; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -111,8 +110,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private static final Interpolator IME_ADJUST_INTERPOLATOR = new PathInterpolator(0.2f, 0f, 0.1f, 1f); - private static final int MSG_RESIZE_STACK = 0; - private DividerHandleView mHandle; private View mBackground; private MinimizedDockShadow mMinimizedShadow; @@ -149,6 +146,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, private SplitDisplayLayout mSplitLayout; private DividerCallbacks mCallback; private final Rect mStableInsets = new Rect(); + private final AnimationHandler mAnimationHandler = new AnimationHandler(); private boolean mGrowRecents; private ValueAnimator mCurrentAnimator; @@ -159,7 +157,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private boolean mHomeStackResizable; private boolean mAdjustedForIme; private DividerState mState; - private final SurfaceFlingerVsyncChoreographer mSfChoreographer; private SplitScreenTaskOrganizer mTiles; boolean mFirstLayout = true; @@ -173,18 +170,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, // used interact with keyguard. private boolean mSurfaceHidden = false; - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_RESIZE_STACK: - resizeStackSurfaces(msg.arg1, msg.arg2, (SnapTarget) msg.obj); - break; - default: - super.handleMessage(msg); - } - } - }; + private final Handler mHandler = new Handler(); private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { @Override @@ -277,11 +263,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mSfChoreographer = new SurfaceFlingerVsyncChoreographer(mHandler, context.getDisplay(), - Choreographer.getInstance()); final DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider()); } @Override @@ -326,7 +311,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, void onDividerRemoved() { mRemoved = true; mCallback = null; - mHandler.removeMessages(MSG_RESIZE_STACK); } @Override @@ -548,7 +532,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget( mStartPosition, 0 /* velocity */, false /* hardDismiss */); - resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget); + resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget); } break; case MotionEvent.ACTION_UP: @@ -633,7 +617,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position); final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE; ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); - anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(), + anim.addUpdateListener(animation -> resizeStackSurfaces((int) animation.getAnimatedValue(), taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f ? TASK_POSITION_SAME : snapTarget.taskPosition, @@ -682,7 +666,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, @Override public void onAnimationCancel(Animator animation) { - mHandler.removeMessages(MSG_RESIZE_STACK); mCancelled = true; } @@ -693,8 +676,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, delay = endDelay; } else if (mCancelled) { delay = 0; - } else if (mSfChoreographer.getSurfaceFlingerOffsetMs() > 0) { - delay = mSfChoreographer.getSurfaceFlingerOffsetMs(); } if (delay == 0) { endAction.accept(mCancelled); @@ -705,6 +686,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } } }); + anim.setAnimationHandler(mAnimationHandler); mCurrentAnimator = anim; return anim; } @@ -1047,13 +1029,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDividerSize); } - public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) { - Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition, - taskSnapTarget); - message.setAsynchronous(true); - mSfChoreographer.scheduleAtSfVsync(mHandler, message); - } - private void resizeStackSurfaces(SnapTarget taskSnapTarget) { resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget); } @@ -1067,7 +1042,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect; otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect; - mDividerPositionX = dockedRect.right; + mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT + ? otherRect.right : dockedRect.right; mDividerPositionY = dockedRect.bottom; if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java index 3b8addb85d85..92f6b4a2d8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -101,7 +101,16 @@ public class SplitDisplayLayout { } int getPrimarySplitSide() { - return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; + switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) { + case DisplayLayout.NAV_BAR_BOTTOM: + return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; + case DisplayLayout.NAV_BAR_LEFT: + return DOCKED_RIGHT; + case DisplayLayout.NAV_BAR_RIGHT: + return DOCKED_LEFT; + default: + return DOCKED_INVALID; + } } boolean isMinimized() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 94afde786e78..24195156d8cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -322,10 +322,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void suppressAmbientDisplay(boolean suppress) { } /** - * @see IStatusBar#showToast(String, IBinder, CharSequence, IBinder, int, + * @see IStatusBar#showToast(int, String, IBinder, CharSequence, IBinder, int, * ITransientNotificationCallback) */ - default void showToast(String packageName, IBinder token, CharSequence text, + default void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { } @@ -798,7 +798,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void showToast(String packageName, IBinder token, CharSequence text, + public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); @@ -807,7 +807,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< args.arg3 = text; args.arg4 = windowToken; args.arg5 = callback; - args.argi1 = duration; + args.argi1 = uid; + args.argi2 = duration; mHandler.obtainMessage(MSG_SHOW_TOAST, args).sendToTarget(); } } @@ -1276,9 +1277,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< IBinder windowToken = (IBinder) args.arg4; ITransientNotificationCallback callback = (ITransientNotificationCallback) args.arg5; - int duration = args.argi1; + int uid = args.argi1; + int duration = args.argi2; for (Callbacks callbacks : mCallbacks) { - callbacks.showToast(packageName, token, text, windowToken, duration, + callbacks.showToast(uid, packageName, token, text, windowToken, duration, callback); } break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index fd44f04a0d80..729f934937da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.WallpaperManager import android.util.Log +import android.util.MathUtils import android.view.Choreographer import android.view.View import androidx.annotation.VisibleForTesting @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.NotificationShadeWindowController import com.android.systemui.statusbar.phone.PanelExpansionListener +import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.FileDescriptor import java.io.PrintWriter @@ -74,6 +76,7 @@ class NotificationShadeDepthController @Inject constructor( var shadeSpring = DepthAnimation() @VisibleForTesting var globalActionsSpring = DepthAnimation() + var showingHomeControls: Boolean = false @VisibleForTesting var brightnessMirrorSpring = DepthAnimation() @@ -105,6 +108,16 @@ class NotificationShadeDepthController @Inject constructor( } /** + * Force stop blur effect when necessary. + */ + private var scrimsVisible: Boolean = false + set(value) { + if (field == value) return + field = value + scheduleUpdate() + } + + /** * Blur radius of the wake-up animation on this frame. */ private var wakeAndUnlockBlurRadius = 0 @@ -133,7 +146,20 @@ class NotificationShadeDepthController @Inject constructor( shadeRadius = 0f } } - val blur = max(shadeRadius.toInt(), globalActionsSpring.radius) + + // Home controls have black background, this means that we should not have blur when they + // are fully visible, otherwise we'll enter Client Composition unnecessarily. + var globalActionsRadius = globalActionsSpring.radius + if (showingHomeControls) { + globalActionsRadius = 0 + } + var blur = max(shadeRadius.toInt(), globalActionsRadius) + + // Make blur be 0 if it is necessary to stop blur effect. + if (scrimsVisible) { + blur = 0 + } + blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) try { wallpaperManager.setWallpaperZoomOut(root.windowToken, @@ -193,6 +219,10 @@ class NotificationShadeDepthController @Inject constructor( brightnessMirrorSpring.finishIfRunning() } } + + override fun onDozeAmountChanged(linear: Float, eased: Float) { + wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased) + } } init { @@ -201,6 +231,10 @@ class NotificationShadeDepthController @Inject constructor( keyguardStateController.addCallback(keyguardStateCallback) } statusBarStateController.addCallback(statusBarStateCallback) + notificationShadeWindowController.setScrimsVisibilityListener { + // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition. + visibility -> scrimsVisible = visibility == ScrimController.OPAQUE + } } /** @@ -216,8 +250,12 @@ class NotificationShadeDepthController @Inject constructor( private fun updateShadeBlur() { var newBlur = 0 - if (statusBarStateController.state == StatusBarState.SHADE) { - newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) + val state = statusBarStateController.state + if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) { + val animatedBlur = + Interpolators.SHADE_ANIMATION.getInterpolation( + MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f)) + newBlur = blurUtils.blurRadiusOfRatio(0.35f * animatedBlur + 0.65f * shadeExpansion) } shadeSpring.animateTo(newBlur) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 1696f0715865..53ec57090321 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -103,12 +103,20 @@ class ConversationNotificationManager @Inject constructor( override fun onEntryInflated(entry: NotificationEntry) { if (!entry.ranking.isConversation) return fun updateCount(isExpanded: Boolean) { - if (isExpanded && !notifPanelCollapsed) { + if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded())) { resetCount(entry.key) entry.row?.let(::resetBadgeUi) } } - entry.row?.setOnExpansionChangedListener(::updateCount) + entry.row?.setOnExpansionChangedListener { isExpanded -> + if (entry.row?.isShown == true && isExpanded) { + entry.row.performOnIntrinsicHeightReached { + updateCount(isExpanded) + } + } else { + updateCount(isExpanded) + } + } updateCount(entry.row?.isExpanded == true) } @@ -169,7 +177,8 @@ class ConversationNotificationManager @Inject constructor( private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = (row.layouts?.asSequence() ?: emptySequence()) - .mapNotNull { layout -> layout.contractedChild as? ConversationLayout } + .flatMap { layout -> layout.allViews.asSequence()} + .mapNotNull { view -> view as? ConversationLayout } .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index c8b34f1f5b27..4e6df0ad1ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -75,6 +75,9 @@ public final class NotificationClicker implements View.OnClickListener { // We never want to open the app directly if the user clicks in between // the notifications. return; + } else if (row.areGutsExposed()) { + // ignore click if guts are exposed + return; } // Mark notification for one frame. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 295adae9c9ee..9324b14bf211 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -555,8 +556,8 @@ public class NotificationEntryManager implements NotificationEntry entry = new NotificationEntry( notification, ranking, - mFgsFeatureController.isForegroundServiceDismissalEnabled()); - mAllNotifications.add(entry); + mFgsFeatureController.isForegroundServiceDismissalEnabled(), + SystemClock.uptimeMillis()); mLeakDetector.trackInstance(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 9c2cac71925e..365862bef871 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -47,6 +47,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -387,7 +388,7 @@ public class NotifCollection implements Dumpable { if (entry == null) { // A new notification! - entry = new NotificationEntry(sbn, ranking); + entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis()); mNotificationSet.put(sbn.getKey(), entry); mLogger.logNotifPosted(sbn.getKey()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index dd7be2775209..68ec34e90760 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -35,6 +35,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationSect import static java.util.Objects.requireNonNull; +import android.annotation.CurrentTimeMillisLong; import android.app.Notification; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; @@ -93,6 +94,7 @@ public final class NotificationEntry extends ListEntry { private final String mKey; private StatusBarNotification mSbn; private Ranking mRanking; + private long mCreationTime; /* * Bookkeeping members @@ -171,21 +173,29 @@ public final class NotificationEntry extends ListEntry { private boolean mAllowFgsDismissal; private int mBucket = BUCKET_ALERTING; + /** + * @param sbn the StatusBarNotification from system server + * @param ranking also from system server + * @param creationTime SystemClock.uptimeMillis of when we were created + */ public NotificationEntry( @NonNull StatusBarNotification sbn, - @NonNull Ranking ranking) { - this(sbn, ranking, false); + @NonNull Ranking ranking, + long creationTime) { + this(sbn, ranking, false, creationTime); } public NotificationEntry( @NonNull StatusBarNotification sbn, @NonNull Ranking ranking, - boolean allowFgsDismissal + boolean allowFgsDismissal, + long creationTime ) { super(requireNonNull(Objects.requireNonNull(sbn).getKey())); requireNonNull(ranking); + mCreationTime = creationTime; mKey = sbn.getKey(); setSbn(sbn); setRanking(ranking); @@ -238,6 +248,21 @@ public final class NotificationEntry extends ListEntry { } /** + * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless + * of any changes to the data presented. It is set once on creation and will never change, and + * allows us to know exactly how long this notification has been alive for in our listener + * service. It is entirely unrelated to the information inside of the notification. + * + * This is different to Notification#when because it persists throughout updates, whereas + * system server treats every single call to notify() as a new notification and we handle + * updates to NotificationEntry locally. + */ + @CurrentTimeMillisLong + public long getCreationTime() { + return mCreationTime; + } + + /** * Should only be called by NotificationEntryManager and friends. * TODO: Make this package-private */ @@ -601,6 +626,13 @@ public final class NotificationEntry extends ListEntry { return row != null && row.isPinned(); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + return row != null && row.isPinnedAndExpanded(); + } + public void setRowPinned(boolean pinned) { if (row != null) row.setPinned(pinned); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index 88cca43fd1a9..5879c15c2493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -83,9 +83,9 @@ class PeopleNotificationIdentifierImpl @Inject constructor( private val Ranking.personTypeInfo get() = when { + !isConversation -> TYPE_NON_PERSON channel?.isImportantConversation == true -> TYPE_IMPORTANT_PERSON - isConversation -> TYPE_PERSON - else -> TYPE_NON_PERSON + else -> TYPE_PERSON } private fun extractPersonTypeInfo(sbn: StatusBarNotification) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 2917346153d2..5c578dfc5744 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -284,6 +284,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isPinned()) { nowExpanded = !mExpandedWhenPinned; mExpandedWhenPinned = nowExpanded; + // Also notify any expansion changed listeners. This is necessary since the + // expansion doesn't actually change (it's already system expanded) but it + // changes visually + if (mExpansionChangedListener != null) { + mExpansionChangedListener.onExpansionChanged(nowExpanded); + } } else { nowExpanded = !isExpanded(); setUserExpanded(nowExpanded); @@ -326,6 +332,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationInlineImageResolver mImageResolver; private NotificationMediaManager mMediaManager; @Nullable private OnExpansionChangedListener mExpansionChangedListener; + @Nullable private Runnable mOnIntrinsicHeightReachedRunnable; private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); @@ -358,6 +365,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return Arrays.copyOf(mLayouts, mLayouts.length); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + if (!isPinned()) { + return false; + } + return mExpandedWhenPinned; + } + @Override public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -2690,6 +2707,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onParentHeightUpdate(); } + handleIntrinsicHeightReached(); } @Override @@ -2907,6 +2925,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mExpansionChangedListener = listener; } + /** + * Perform an action when the notification height has reached its intrinsic height. + * + * @param runnable the runnable to run + */ + public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) { + mOnIntrinsicHeightReachedRunnable = runnable; + handleIntrinsicHeightReached(); + } + + private void handleIntrinsicHeightReached() { + if (mOnIntrinsicHeightReachedRunnable != null + && getActualHeight() == getIntrinsicHeight()) { + mOnIntrinsicHeightReachedRunnable.run(); + mOnIntrinsicHeightReachedRunnable = null; + } + } + @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index 893e8490eb90..e4e3ebcf7671 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -151,9 +151,12 @@ public final class NotifBindPipeline { * the real work once rather than repeatedly start and cancel it. */ private void requestPipelineRun(NotificationEntry entry) { - mLogger.logRequestPipelineRun(entry.getKey()); - final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry.row == null) { + // Row is not managed yet but may be soon. Stop for now. + return; + } + mLogger.logRequestPipelineRun(entry.getKey()); // Abort any existing pipeline run mStage.abortStage(entry, bindEntry.row); @@ -177,10 +180,6 @@ public final class NotifBindPipeline { final BindEntry bindEntry = mBindEntries.get(entry); final ExpandableNotificationRow row = bindEntry.row; - if (row == null) { - // Row is not managed yet but may be soon. Stop for now. - return; - } mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index b18bf01ea91f..3c3f1b21fb3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; @@ -988,6 +989,14 @@ public class NotificationContentView extends FrameLayout { } } + public @NonNull View[] getAllViews() { + return new View[] { + mContractedChild, + mHeadsUpChild, + mExpandedChild, + mSingleLineView }; + } + public NotificationViewWrapper getVisibleWrapper(int visibleType) { switch (visibleType) { case VISIBLE_TYPE_EXPANDED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 5205bab8fea3..7ac066277c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -169,7 +169,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @Override public void onContentUpdated(ExpandableNotificationRow row) { super.onContentUpdated(row); - mIsLowPriority = row.isLowPriority(); + mIsLowPriority = row.getEntry().isAmbient(); mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren(); ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews(); 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 f06cfec9480a..82e02b47974c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -203,7 +203,9 @@ public class KeyguardBouncer { Log.wtf(TAG, "onFullyShown when view was null"); } else { mKeyguardView.onResume(); - mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + if (mRoot != null) { + mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index d70484e9cf41..a19d35ac4e81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -50,19 +50,22 @@ public class LockIcon extends KeyguardAffordanceView { static final int STATE_BIOMETRICS_ERROR = 3; private float mDozeAmount; private int mIconColor; - private StateProvider mStateProvider; private int mOldState; + private int mState; private boolean mPulsing; private boolean mDozing; private boolean mKeyguardJustShown; + private boolean mPredrawRegistered; private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); + mPredrawRegistered = false; - int newState = mStateProvider.getState(); + int newState = mState; + mOldState = mState; Drawable icon = getIcon(newState); setImageDrawable(icon, false); @@ -80,7 +83,7 @@ public class LockIcon extends KeyguardAffordanceView { @Override public void onAnimationEnd(Drawable drawable) { if (getDrawable() == animation - && newState == mStateProvider.getState() + && newState == mState && newState == STATE_SCANNING_FACE) { animation.start(); } else { @@ -100,10 +103,6 @@ public class LockIcon extends KeyguardAffordanceView { super(context, attrs); } - void setStateProvider(StateProvider stateProvider) { - mStateProvider = stateProvider; - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -135,13 +134,16 @@ public class LockIcon extends KeyguardAffordanceView { return false; } - void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { - mOldState = oldState; + void update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { + mState = newState; mPulsing = pulsing; mDozing = dozing; mKeyguardJustShown = keyguardJustShown; - getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + if (!mPredrawRegistered) { + mPredrawRegistered = true; + getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + } } void setDozeAmount(float dozeAmount) { @@ -175,7 +177,7 @@ public class LockIcon extends KeyguardAffordanceView { return mDrawableCache.get(iconRes); } - static int getIconForState(int state) { + private static int getIconForState(int state) { int iconRes; switch (state) { case STATE_LOCKED: @@ -196,7 +198,7 @@ public class LockIcon extends KeyguardAffordanceView { return iconRes; } - static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, + private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { // Never animate when screen is off @@ -260,9 +262,4 @@ public class LockIcon extends KeyguardAffordanceView { } return LOCK_ANIM_RES_IDS[0][lockAnimIndex]; } - - interface StateProvider { - int getState(); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java index f7c861b84a68..a633e1979bad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java @@ -352,7 +352,6 @@ public class LockscreenLockIconController { mLockIcon.setOnClickListener(this::handleClick); mLockIcon.setOnLongClickListener(this::handleLongClick); mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); - mLockIcon.setStateProvider(this::getState); if (mLockIcon.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon); @@ -462,7 +461,7 @@ public class LockscreenLockIconController { shouldUpdate = false; } if (shouldUpdate && mLockIcon != null) { - mLockIcon.update(mLastState, mPulsing, mDozing, mKeyguardJustShown); + mLockIcon.update(state, mPulsing, mDozing, mKeyguardJustShown); } mLastState = state; mKeyguardJustShown = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 6fd3bb2c8222..3105155f28e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -26,6 +26,7 @@ import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE; import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; @@ -56,6 +57,8 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.Binder; @@ -71,6 +74,7 @@ import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; import android.view.Display; +import android.view.Gravity; import android.view.InsetsState.InternalInsetsType; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -104,6 +108,7 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; @@ -133,7 +138,7 @@ import dagger.Lazy; * on clicks and view states of the nav bar. */ public class NavigationBarFragment extends LifecycleFragment implements Callbacks, - NavigationModeController.ModeChangedListener { + NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener { public static final String TAG = "NavigationBar"; private static final boolean DEBUG = false; @@ -141,6 +146,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private static final String EXTRA_DISABLE2_STATE = "disabled2_state"; private static final String EXTRA_APPEARANCE = "appearance"; private static final String EXTRA_TRANSIENT_STATE = "transient_state"; + private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; @@ -199,6 +206,23 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private boolean mIsOnDefaultDisplay; public boolean mHomeBlockedThisTouch; + /** + * When user is QuickSwitching between apps of different orientations, we'll draw a fake + * home handle on the orientation they originally touched down to start their swipe + * gesture to indicate to them that they can continue in that orientation without having to + * rotate the phone + * The secondary handle will show when we get + * {@link OverviewProxyListener#onQuickSwitchToNewTask(int)} callback with the + * original handle hidden and we'll flip the visibilities once the + * {@link #mTasksFrozenListener} fires + */ + private NavigationHandle mOrientationHandle; + private WindowManager.LayoutParams mOrientationParams; + private boolean mFrozenTasks; + private int mStartingQuickSwitchRotation; + private int mCurrentRotation; + private boolean mFixedRotationEnabled; + /** Only for default display */ @Nullable private AssistHandleViewController mAssistHandlerViewController; @@ -249,6 +273,12 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + mStartingQuickSwitchRotation = rotation; + orientSecondaryHomeHandle(); + } + + @Override public void startAssistant(Bundle bundle) { mAssistManager.startAssist(bundle); } @@ -271,6 +301,22 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private TaskStackChangeListener mTasksFrozenListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + mFrozenTasks = frozen; + orientSecondaryHomeHandle(); + } + }; + + private NavigationBarTransitions.DarkIntensityListener mOrientationHandleIntensityListener = + new NavigationBarTransitions.DarkIntensityListener() { + @Override + public void onDarkIntensity(float darkIntensity) { + mOrientationHandle.setDarkIntensity(darkIntensity); + } + }; + private final ContextButtonListener mRotationButtonListener = (button, visible) -> { if (visible) { // If the button will actually become visible and the navbar is about to hide, @@ -294,6 +340,14 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private final ContentObserver mFixedRotationObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updatedFixedRotation(); + } + }; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -351,6 +405,10 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), + false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); + if (savedInstanceState != null) { mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0); @@ -376,6 +434,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavigationModeController.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mAssistContentObserver); + mContentResolver.unregisterContentObserver(mFixedRotationObserver); DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); } @@ -406,6 +465,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } mNavigationBarView.setNavigationIconHints(mNavigationIconHints); mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); + updatedFixedRotation(); prepareNavigationBarView(); checkNavBarModes(); @@ -442,6 +502,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback new AssistHandleViewController(mHandler, mNavigationBarView); getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController); } + + initSecondaryHomeHandleForRotation(); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTasksFrozenListener); } @Override @@ -458,6 +521,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } mOverviewProxyService.removeCallback(mOverviewProxyListener); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTasksFrozenListener); + if (mOrientationHandle != null) { + resetSecondaryHandle(); + getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this); + getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); + mWindowManager.removeView(mOrientationHandle); + } } @Override @@ -490,6 +560,90 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback repositionNavigationBar(); } + private void initSecondaryHomeHandleForRotation() { + if (!canShowSecondaryHandle()) { + return; + } + + getContext().getSystemService(DisplayManager.class) + .registerDisplayListener(this, new Handler(Looper.getMainLooper())); + + mOrientationHandle = new VerticalNavigationHandle(getContext()); + + getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener); + mOrientationParams = new WindowManager.LayoutParams(0, 0, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT); + mWindowManager.addView(mOrientationHandle, mOrientationParams); + mOrientationHandle.setVisibility(View.GONE); + } + + private void orientSecondaryHomeHandle() { + if (!canShowSecondaryHandle()) { + return; + } + + if (!mFrozenTasks) { + resetSecondaryHandle(); + } else { + int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); + int height = 0; + int width = 0; + Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); + switch (deltaRotation) { + case Surface.ROTATION_90: + case Surface.ROTATION_270: + height = dispSize.height(); + width = getResources() + .getDimensionPixelSize(R.dimen.navigation_bar_height); + break; + case Surface.ROTATION_180: + case Surface.ROTATION_0: + // TODO(b/152683657): Need to determine best UX for this + resetSecondaryHandle(); + return; + } + + mOrientationParams.gravity = + deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT; + mOrientationParams.height = height; + mOrientationParams.width = width; + mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); + mNavigationBarView.setVisibility(View.GONE); + mOrientationHandle.setVisibility(View.VISIBLE); + } + } + + private void resetSecondaryHandle() { + if (mOrientationHandle != null) { + // Case where nav mode is changed w/o ever invoking a quickstep + // mOrientedHandle is initialized lazily + mOrientationHandle.setVisibility(View.GONE); + } + if (mNavigationBarView != null) { + mNavigationBarView.setVisibility(View.VISIBLE); + } + } + + private int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + private void updatedFixedRotation() { + mFixedRotationEnabled = Settings.Global.getInt(mContentResolver, + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + if (!canShowSecondaryHandle()) { + resetSecondaryHandle(); + } + } + @Override public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { if (mNavigationBarView != null) { @@ -1112,6 +1266,10 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavBarMode = mode; updateScreenPinningGestures(); + if (!canShowSecondaryHandle()) { + resetSecondaryHandle(); + } + // Workaround for b/132825155, for secondary users, we currently don't receive configuration // changes on overlay package change since SystemUI runs for the system user. In this case, // trigger a new configuration change to ensure that the nav bar is updated in the same way. @@ -1156,6 +1314,34 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private final AccessibilityServicesStateChangeListener mAccessibilityListener = this::updateAccessibilityServicesState; + @Override + public void onDisplayAdded(int displayId) { + + } + + @Override + public void onDisplayRemoved(int displayId) { + + } + + @Override + public void onDisplayChanged(int displayId) { + if (!canShowSecondaryHandle()) { + return; + } + + int rotation = getContext().getResources().getConfiguration() + .windowConfiguration.getRotation(); + if (rotation != mCurrentRotation) { + mCurrentRotation = rotation; + orientSecondaryHomeHandle(); + } + } + + private boolean canShowSecondaryHandle() { + return mFixedRotationEnabled && mNavBarMode == NAV_BAR_MODE_GESTURAL; + } + private final Consumer<Integer> mRotationWatcher = rotation -> { if (mNavigationBarView != null && mNavigationBarView.needsReorient(rotation)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java index abceb11b36e9..b87479505d00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java @@ -32,11 +32,11 @@ import com.android.systemui.R; public class NavigationHandle extends View implements ButtonInterface { - private final Paint mPaint = new Paint(); + protected final Paint mPaint = new Paint(); private @ColorInt final int mLightColor; private @ColorInt final int mDarkColor; - private final int mRadius; - private final int mBottom; + protected final int mRadius; + protected final int mBottom; private boolean mRequiresInvalidate; public NavigationHandle(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index e1e679f06eef..462b42a44c17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -61,6 +61,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; @@ -92,6 +93,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, private final State mCurrentState = new State(); private OtherwisedCollapsedListener mListener; private ForcePluginOpenListener mForcePluginOpenListener; + private Consumer<Integer> mScrimsVisibilityListener; private final ArrayList<WeakReference<StatusBarWindowCallback>> mCallbacks = Lists.newArrayList(); @@ -150,6 +152,16 @@ public class NotificationShadeWindowController implements Callback, Dumpable, mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback)); } + /** + * Register a listener to monitor scrims visibility + * @param listener A listener to monitor scrims visibility + */ + public void setScrimsVisibilityListener(Consumer<Integer> listener) { + if (listener != null && mScrimsVisibilityListener != listener) { + mScrimsVisibilityListener = listener; + } + } + private boolean shouldEnableKeyguardScreenRotation() { Resources res = mContext.getResources(); return SystemProperties.getBoolean("lockscreen.rot_override", false) @@ -477,6 +489,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, public void setScrimsVisibility(int scrimsVisibility) { mCurrentState.mScrimsVisibility = scrimsVisibility; apply(mCurrentState); + mScrimsVisibilityListener.accept(scrimsVisibility); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 14af466a2424..d4f4d3bfbb54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -39,6 +39,8 @@ import android.telecom.TelecomManager; import android.text.format.DateFormat; import android.util.Log; +import androidx.lifecycle.Observer; + import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -64,6 +66,7 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.time.DateFormatUtil; import java.util.Locale; @@ -109,7 +112,6 @@ public class PhoneStatusBarPolicy private final SharedPreferences mSharedPreferences; private final DateFormatUtil mDateFormatUtil; private final TelecomManager mTelecomManager; - private final AudioManager mAudioManager; private final Handler mHandler = new Handler(); private final CastController mCast; @@ -132,6 +134,7 @@ public class PhoneStatusBarPolicy private final Executor mUiBgExecutor; private final SensorPrivacyController mSensorPrivacyController; private final RecordingController mRecordingController; + private final RingerModeTracker mRingerModeTracker; private boolean mZenVisible; private boolean mVolumeVisible; @@ -154,10 +157,11 @@ public class PhoneStatusBarPolicy KeyguardStateController keyguardStateController, LocationController locationController, SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager, - AlarmManager alarmManager, UserManager userManager, AudioManager audioManager, + AlarmManager alarmManager, UserManager userManager, RecordingController recordingController, @Nullable TelecomManager telecomManager, @DisplayId int displayId, - @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil) { + @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, + RingerModeTracker ringerModeTracker) { mIconController = iconController; mCommandQueue = commandQueue; mBroadcastDispatcher = broadcastDispatcher; @@ -179,8 +183,8 @@ public class PhoneStatusBarPolicy mSensorPrivacyController = sensorPrivacyController; mRecordingController = recordingController; mUiBgExecutor = uiBgExecutor; - mAudioManager = audioManager; mTelecomManager = telecomManager; + mRingerModeTracker = ringerModeTracker; mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot); @@ -208,8 +212,7 @@ public class PhoneStatusBarPolicy public void init() { // listen for broadcasts IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.ACTION_HEADSET_PLUG); filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); @@ -217,6 +220,10 @@ public class PhoneStatusBarPolicy filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); + Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen); + + mRingerModeTracker.getRingerMode().observeForever(observer); + mRingerModeTracker.getRingerModeInternal().observeForever(observer); // listen for user / profile change. try { @@ -350,14 +357,18 @@ public class PhoneStatusBarPolicy } if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) { - if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { - volumeVisible = true; - volumeIconId = R.drawable.stat_sys_ringer_vibrate; - volumeDescription = mResources.getString(R.string.accessibility_ringer_vibrate); - } else if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - volumeVisible = true; - volumeIconId = R.drawable.stat_sys_ringer_silent; - volumeDescription = mResources.getString(R.string.accessibility_ringer_silent); + final Integer ringerModeInternal = + mRingerModeTracker.getRingerModeInternal().getValue(); + if (ringerModeInternal != null) { + if (ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { + volumeVisible = true; + volumeIconId = R.drawable.stat_sys_ringer_vibrate; + volumeDescription = mResources.getString(R.string.accessibility_ringer_vibrate); + } else if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) { + volumeVisible = true; + volumeIconId = R.drawable.stat_sys_ringer_silent; + volumeDescription = mResources.getString(R.string.accessibility_ringer_silent); + } } } @@ -616,10 +627,6 @@ public class PhoneStatusBarPolicy public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { - case AudioManager.RINGER_MODE_CHANGED_ACTION: - case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION: - updateVolumeZen(); - break; case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java new file mode 100644 index 000000000000..a15ca9532a88 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java @@ -0,0 +1,47 @@ +/* + * 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.statusbar.phone; + +import android.content.Context; +import android.graphics.Canvas; + +import com.android.systemui.R; + +/** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */ +public class VerticalNavigationHandle extends NavigationHandle { + private final int mWidth; + + public VerticalNavigationHandle(Context context) { + super(context); + mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width); + } + + @Override + protected void onDraw(Canvas canvas) { + int left; + int top; + int bottom; + int right; + + int radiusOffset = mRadius * 2; + right = getWidth() - mBottom; + top = getHeight() / 2 - (mWidth / 2); /* (height of screen / 2) - (height of bar / 2) */ + left = getWidth() - mBottom - radiusOffset; + bottom = getHeight() / 2 + (mWidth / 2); + canvas.drawRoundRect(left, top, right, bottom, mRadius, mRadius, mPaint); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index bf5900ff24bb..95b41a141244 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -71,7 +71,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC protected boolean mPluggedIn; protected boolean mCharging; private boolean mCharged; - private boolean mPowerSave; + protected boolean mPowerSave; private boolean mAodPowerSave; private boolean mTestmode = false; private boolean mHasReceivedBattery = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java index 7d5498157136..45e47f1f763f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java @@ -21,6 +21,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -294,6 +296,21 @@ public class KeyguardUserSwitcher { return convertView; } + private static Drawable getDrawable(Context context, + UserSwitcherController.UserRecord item) { + Drawable drawable = getIconDrawable(context, item); + int iconColorRes = item.isCurrent ? R.color.kg_user_switcher_selected_avatar_icon_color + : R.color.kg_user_switcher_avatar_icon_color; + drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme())); + + if (item.isCurrent) { + Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected); + drawable = new LayerDrawable(new Drawable[]{bg, drawable}); + } + + return drawable; + } + @Override public void onClick(View v) { UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 69eaaa4f54a9..bb0b5e00ff67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -31,7 +31,6 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Handler; @@ -52,7 +51,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.UserIcons; import com.android.settingslib.RestrictedLockUtilsInternal; -import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.Prefs; @@ -684,18 +682,17 @@ public class UserSwitcherController implements Dumpable { } } - public Drawable getDrawable(Context context, UserRecord item) { + protected static Drawable getIconDrawable(Context context, UserRecord item) { + int iconRes; if (item.isAddUser) { - return context.getDrawable(R.drawable.ic_add_circle_qs); - } - Drawable icon = UserIcons.getDefaultUserIcon( - context.getResources(), item.resolveId(), /* light= */ false); - if (item.isGuest) { - icon.setColorFilter(Utils.getColorAttrDefaultColor(context, - android.R.attr.colorForeground), - Mode.SRC_IN); + iconRes = R.drawable.ic_add_circle; + } else if (item.isGuest) { + iconRes = R.drawable.ic_avatar_guest_user; + } else { + iconRes = R.drawable.ic_avatar_user; } - return icon; + + return context.getDrawable(iconRes); } public void refresh() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 07985ab5a43c..02ae1f8afcdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar; import javax.inject.Inject; import javax.inject.Singleton; @@ -66,7 +67,8 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { // If the system process isn't there we're doomed anyway. } - new AudioRecordingDisclosureBar(mContext).start(); + // Creating AudioRecordingDisclosureBar and just letting it run + new AudioRecordingDisclosureBar(mContext); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java new file mode 100644 index 000000000000..87b3956060f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.tv.micdisclosure; + +import android.content.Context; + +import java.util.Set; + +/** + * A base class for implementing observers for different kinds of activities related to audio + * recording. These observers are to be initialized by {@link AudioRecordingDisclosureBar} and to + * report back to it. + */ +abstract class AudioActivityObserver { + + interface OnAudioActivityStateChangeListener { + void onAudioActivityStateChange(boolean active, String packageName); + } + + final Context mContext; + + final OnAudioActivityStateChangeListener mListener; + + AudioActivityObserver(Context context, OnAudioActivityStateChangeListener listener) { + mContext = context; + mListener = listener; + } + + abstract Set<String> getActivePackages(); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java index e70e30a5ab57..914868301d72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.tv; +package com.android.systemui.statusbar.tv.micdisclosure; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -25,7 +25,6 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.IntDef; import android.annotation.UiThread; -import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -40,6 +39,7 @@ import android.view.WindowManager; import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.statusbar.tv.TvStatusBar; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -54,9 +54,10 @@ import java.util.Set; * * @see TvStatusBar */ -class AudioRecordingDisclosureBar { - private static final String TAG = "AudioRecordingDisclosureBar"; - private static final boolean DEBUG = false; +public class AudioRecordingDisclosureBar implements + AudioActivityObserver.OnAudioActivityStateChangeListener { + private static final String TAG = "AudioRecordingDisclosure"; + static final boolean DEBUG = false; // This title is used to test the microphone disclosure indicator in // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest @@ -98,10 +99,12 @@ class AudioRecordingDisclosureBar { private TextView mTextView; @State private int mState = STATE_NOT_SHOWN; + /** - * Set of the applications that currently are conducting audio recording. + * Array of the observers that monitor different aspects of the system, such as AppOps and + * microphone foreground services */ - private final Set<String> mActiveAudioRecordingPackages = new ArraySet<>(); + private final AudioActivityObserver[] mAudioActivityObservers; /** * Set of applications that we've notified the user about since the indicator came up. Meaning * that if an application is in this list then at some point since the indicator came up, it @@ -119,29 +122,52 @@ class AudioRecordingDisclosureBar { * one of the two aforementioned states. */ private final Queue<String> mPendingNotificationPackages = new LinkedList<>(); + /** + * Set of applications for which we make an exception and do not show the indicator. This gets + * populated once - in {@link #AudioRecordingDisclosureBar(Context)}. + */ + private final Set<String> mExemptPackages; - AudioRecordingDisclosureBar(Context context) { + public AudioRecordingDisclosureBar(Context context) { mContext = context; - } - void start() { - // Register AppOpsManager callback - final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService( - Context.APP_OPS_SERVICE); - appOpsManager.startWatchingActive( - new String[]{AppOpsManager.OPSTR_RECORD_AUDIO}, - mContext.getMainExecutor(), - new OnActiveRecordingListener()); + mExemptPackages = new ArraySet<>( + Arrays.asList(mContext.getResources().getStringArray( + R.array.audio_recording_disclosure_exempt_apps))); + + mAudioActivityObservers = new AudioActivityObserver[]{ + new RecordAudioAppOpObserver(mContext, this), + new MicrophoneForegroundServicesObserver(mContext, this), + }; } @UiThread - private void onStartedRecording(String packageName) { - if (!mActiveAudioRecordingPackages.add(packageName)) { - // This app is already known to perform recording + @Override + public void onAudioActivityStateChange(boolean active, String packageName) { + if (DEBUG) { + Log.d(TAG, + "onAudioActivityStateChange, packageName=" + packageName + ", active=" + + active); + } + + if (mExemptPackages.contains(packageName)) { + if (DEBUG) Log.d(TAG, " - exempt package: ignoring"); return; } + + if (active) { + showIndicatorForPackageIfNeeded(packageName); + } else { + hideIndicatorIfNeeded(); + } + } + + @UiThread + private void showIndicatorForPackageIfNeeded(String packageName) { + if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName); if (!mSessionNotifiedPackages.add(packageName)) { // We've already notified user about this app, no need to do it again. + if (DEBUG) Log.d(TAG, " - already notified"); return; } @@ -167,23 +193,33 @@ class AudioRecordingDisclosureBar { } @UiThread - private void onDoneRecording(String packageName) { - if (!mActiveAudioRecordingPackages.remove(packageName)) { - // Was not marked as an active recorder, do nothing - return; - } - + private void hideIndicatorIfNeeded() { + if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded"); // If not MINIMIZED, will check whether the indicator should be hidden when the indicator - // comes to the STATE_MINIMIZED eventually. If is in the STATE_MINIMIZED, but there are - // other active recorders - simply ignore. - if (mState == STATE_MINIMIZED && mActiveAudioRecordingPackages.isEmpty()) { - mSessionNotifiedPackages.clear(); - hide(); + // comes to the STATE_MINIMIZED eventually. + if (mState != STATE_MINIMIZED) return; + + // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore. + for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) { + for (String activePackage : mAudioActivityObservers[index].getActivePackages()) { + if (mExemptPackages.contains(activePackage)) continue; + if (DEBUG) Log.d(TAG, " - there are still ongoing activities"); + return; + } } + + // Clear the state and hide the indicator. + mSessionNotifiedPackages.clear(); + hide(); } @UiThread private void show(String packageName) { + final String label = getApplicationLabel(packageName); + if (DEBUG) { + Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")..."); + } + // Inflate the indicator view mIndicatorView = LayoutInflater.from(mContext).inflate( R.layout.tv_audio_recording_indicator, @@ -196,7 +232,6 @@ class AudioRecordingDisclosureBar { mBgRight = mIndicatorView.findViewById(R.id.bg_right); // Set up the notification text - final String label = getApplicationLabel(packageName); mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); // Initially change the visibility to INVISIBLE, wait until and receives the size and @@ -260,6 +295,9 @@ class AudioRecordingDisclosureBar { @UiThread private void expand(String packageName) { final String label = getApplicationLabel(packageName); + if (DEBUG) { + Log.d(TAG, "Expanding for " + packageName + " (" + label + ")..."); + } mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); final AnimatorSet set = new AnimatorSet(); @@ -283,6 +321,7 @@ class AudioRecordingDisclosureBar { @UiThread private void minimize() { + if (DEBUG) Log.d(TAG, "Minimizing..."); final int targetOffset = mTextsContainers.getWidth(); final AnimatorSet set = new AnimatorSet(); set.playTogether( @@ -305,6 +344,7 @@ class AudioRecordingDisclosureBar { @UiThread private void hide() { + if (DEBUG) Log.d(TAG, "Hiding..."); final int targetOffset = mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX(); final AnimatorSet set = new AnimatorSet(); @@ -326,6 +366,7 @@ class AudioRecordingDisclosureBar { @UiThread private void onExpanded() { + if (DEBUG) Log.d(TAG, "Expanded"); mState = STATE_SHOWN; mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION); @@ -333,20 +374,21 @@ class AudioRecordingDisclosureBar { @UiThread private void onMinimized() { + if (DEBUG) Log.d(TAG, "Minimized"); mState = STATE_MINIMIZED; if (!mPendingNotificationPackages.isEmpty()) { // There is a new application that started recording, tell the user about it. expand(mPendingNotificationPackages.poll()); - } else if (mActiveAudioRecordingPackages.isEmpty()) { - // Nobody is recording anymore, clear state and remove the indicator. - mSessionNotifiedPackages.clear(); - hide(); + } else { + hideIndicatorIfNeeded(); } } @UiThread private void onHidden() { + if (DEBUG) Log.d(TAG, "Hidden"); + final WindowManager windowManager = (WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE); windowManager.removeView(mIndicatorView); @@ -392,35 +434,4 @@ class AudioRecordingDisclosureBar { } return pm.getApplicationLabel(appInfo).toString(); } - - private class OnActiveRecordingListener implements AppOpsManager.OnOpActiveChangedListener { - private final Set<String> mExemptApps; - - private OnActiveRecordingListener() { - mExemptApps = new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray( - R.array.audio_recording_disclosure_exempt_apps))); - } - - @Override - public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { - if (DEBUG) { - Log.d(TAG, - "OP_RECORD_AUDIO active change, active=" + active + ", app=" - + packageName); - } - - if (mExemptApps.contains(packageName)) { - if (DEBUG) { - Log.d(TAG, "\t- exempt app"); - } - return; - } - - if (active) { - onStartedRecording(packageName); - } else { - onDoneRecording(packageName); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java new file mode 100644 index 000000000000..1ede88a26020 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.tv.micdisclosure; + +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; + +import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG; + +import android.annotation.UiThread; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.app.IProcessObserver; +import android.content.Context; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The purpose of these class is to detect packages that are running foreground services of type + * 'microphone' and to report back to {@link AudioRecordingDisclosureBar}. + */ +class MicrophoneForegroundServicesObserver extends AudioActivityObserver { + private static final String TAG = "MicrophoneForegroundServicesObserver"; + private static final boolean ENABLED = true; + + private final IActivityManager mActivityManager; + /** + * A dictionary that maps PIDs to the package names. We only keep track of the PIDs that are + * "active" (those that are running FGS with FOREGROUND_SERVICE_TYPE_MICROPHONE flag). + */ + private final SparseArray<String[]> mPidToPackages = new SparseArray<>(); + /** + * A dictionary that maps "active" packages to the number of the "active" processes associated + * with those packages. We really only need this in case when one application is running in + * multiple processes, so that we don't lose track of the package when one of its "active" + * processes ceases, while others remain "active". + */ + private final Map<String, Integer> mPackageToProcessCount = new ArrayMap<>(); + + MicrophoneForegroundServicesObserver(Context context, + OnAudioActivityStateChangeListener listener) { + super(context, listener); + + mActivityManager = ActivityManager.getService(); + try { + mActivityManager.registerProcessObserver(mProcessObserver); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't register process observer", e); + } + } + + @Override + Set<String> getActivePackages() { + return ENABLED ? mPackageToProcessCount.keySet() : Collections.emptySet(); + } + + @UiThread + private void onProcessForegroundServicesChanged(int pid, boolean hasMicFgs) { + final String[] changedPackages; + if (hasMicFgs) { + if (mPidToPackages.contains(pid)) { + // We are already tracking this pid - ignore. + changedPackages = null; + } else { + changedPackages = getPackageNames(pid); + mPidToPackages.append(pid, changedPackages); + } + } else { + changedPackages = mPidToPackages.removeReturnOld(pid); + } + + if (changedPackages == null) { + return; + } + + for (int index = changedPackages.length - 1; index >= 0; index--) { + final String packageName = changedPackages[index]; + int processCount = mPackageToProcessCount.getOrDefault(packageName, 0); + final boolean shouldNotify; + if (hasMicFgs) { + processCount++; + shouldNotify = processCount == 1; + } else { + processCount--; + shouldNotify = processCount == 0; + } + if (processCount > 0) { + mPackageToProcessCount.put(packageName, processCount); + } else { + mPackageToProcessCount.remove(packageName); + } + if (shouldNotify) notifyPackageStateChanged(packageName, hasMicFgs); + } + } + + @UiThread + private void onProcessDied(int pid) { + final String[] packages = mPidToPackages.removeReturnOld(pid); + if (packages == null) { + // This PID was not active - ignore. + return; + } + + for (int index = packages.length - 1; index >= 0; index--) { + final String packageName = packages[index]; + int processCount = mPackageToProcessCount.getOrDefault(packageName, 0); + if (processCount <= 0) { + Log.e(TAG, "Bookkeeping error, process count for " + packageName + " is " + + processCount); + continue; + } + processCount--; + if (processCount > 0) { + mPackageToProcessCount.put(packageName, processCount); + } else { + mPackageToProcessCount.remove(packageName); + notifyPackageStateChanged(packageName, false); + } + } + } + + @UiThread + private void notifyPackageStateChanged(String packageName, boolean active) { + if (active) { + if (DEBUG) Log.d(TAG, "New microphone fgs detected, package=" + packageName); + } else { + if (DEBUG) Log.d(TAG, "Microphone fgs is gone, package=" + packageName); + } + + if (ENABLED) mListener.onAudioActivityStateChange(active, packageName); + } + + @UiThread + private String[] getPackageNames(int pid) { + final List<ActivityManager.RunningAppProcessInfo> runningApps; + try { + runningApps = mActivityManager.getRunningAppProcesses(); + } catch (RemoteException e) { + Log.d(TAG, "Couldn't get package name for pid=" + pid); + return null; + } + if (runningApps == null) { + Log.wtf(TAG, "No running apps reported"); + } + for (ActivityManager.RunningAppProcessInfo app : runningApps) { + if (app.pid == pid) { + return app.pkgList; + } + } + return null; + } + + private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() { + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {} + + @Override + public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { + mContext.getMainExecutor().execute(() -> onProcessForegroundServicesChanged(pid, + (serviceTypes & FOREGROUND_SERVICE_TYPE_MICROPHONE) != 0)); + } + + @Override + public void onProcessDied(int pid, int uid) { + mContext.getMainExecutor().execute( + () -> MicrophoneForegroundServicesObserver.this.onProcessDied(pid)); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java new file mode 100644 index 000000000000..b5b1c2b3018a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.tv.micdisclosure; + +import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG; + +import android.annotation.UiThread; +import android.app.AppOpsManager; +import android.content.Context; +import android.util.ArraySet; +import android.util.Log; + +import java.util.Set; + +/** + * The purpose of these class is to detect packages that are conducting audio recording (according + * to {@link AppOpsManager}) and report this to {@link AudioRecordingDisclosureBar}. + */ +class RecordAudioAppOpObserver extends AudioActivityObserver implements + AppOpsManager.OnOpActiveChangedListener { + private static final String TAG = "RecordAudioAppOpObserver"; + + /** + * Set of the applications that currently are conducting audio recording according to {@link + * AppOpsManager}. + */ + private final Set<String> mActiveAudioRecordingPackages = new ArraySet<>(); + + RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) { + super(context, listener); + + // Register AppOpsManager callback + final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService( + Context.APP_OPS_SERVICE); + appOpsManager.startWatchingActive( + new String[]{AppOpsManager.OPSTR_RECORD_AUDIO}, + mContext.getMainExecutor(), + this); + } + + @UiThread + @Override + Set<String> getActivePackages() { + return mActiveAudioRecordingPackages; + } + + @UiThread + @Override + public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { + if (DEBUG) { + Log.d(TAG, + "OP_RECORD_AUDIO active change, active=" + active + ", package=" + + packageName); + } + + if (active) { + if (mActiveAudioRecordingPackages.add(packageName)) { + mListener.onAudioActivityStateChange(true, packageName); + } + } else { + if (mActiveAudioRecordingPackages.remove(packageName)) { + mListener.onAudioActivityStateChange(false, packageName); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 9ccb9bf5b62b..9b465ae15478 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -24,10 +24,10 @@ import android.content.Context; import android.content.res.Resources; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.view.View; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; import android.widget.ToastPresenter; import com.android.internal.R; @@ -48,9 +48,8 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { private static final String TAG = "ToastUI"; private final CommandQueue mCommandQueue; - private final WindowManager mWindowManager; private final INotificationManager mNotificationManager; - private final AccessibilityManager mAccessibilityManager; + private final IAccessibilityManager mAccessibilityManager; private final int mGravity; private final int mY; @Nullable private ToastPresenter mPresenter; @@ -59,18 +58,17 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @Inject public ToastUI(Context context, CommandQueue commandQueue) { this(context, commandQueue, - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE), INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)), - AccessibilityManager.getInstance(context)); + IAccessibilityManager.Stub.asInterface( + ServiceManager.getService(Context.ACCESSIBILITY_SERVICE))); } @VisibleForTesting - ToastUI(Context context, CommandQueue commandQueue, WindowManager windowManager, - INotificationManager notificationManager, AccessibilityManager accessibilityManager) { + ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager, + @Nullable IAccessibilityManager accessibilityManager) { super(context); mCommandQueue = commandQueue; - mWindowManager = windowManager; mNotificationManager = notificationManager; mAccessibilityManager = accessibilityManager; Resources resources = mContext.getResources(); @@ -85,15 +83,16 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @Override @MainThread - public void showToast(String packageName, IBinder token, CharSequence text, + public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { if (mPresenter != null) { hideCurrentToast(); } - View view = ToastPresenter.getTextToastView(mContext, text); + Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0); + View view = ToastPresenter.getTextToastView(context, text); mCallback = callback; - mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager, - mNotificationManager, packageName); + mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager, + packageName); mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback); } diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt new file mode 100644 index 000000000000..d65b285adb0c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.graphics.PointF +import android.os.Handler +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewConfiguration +import kotlin.math.hypot + +/** + * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about + * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the + * view's initial position. + */ +abstract class RelativeTouchListener : View.OnTouchListener { + + /** + * Called when an ACTION_DOWN event is received for the given view. + * + * @return False if the object is not interested in MotionEvents at this time, or true if we + * should consume this event and subsequent events, and begin calling [onMove]. + */ + abstract fun onDown(v: View, ev: MotionEvent): Boolean + + /** + * Called when an ACTION_MOVE event is received for the given view. This signals that the view + * is being dragged. + * + * @param viewInitialX The view's translationX value when this touch gesture started. + * @param viewInitialY The view's translationY value when this touch gesture started. + * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels. + * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels. + */ + abstract fun onMove( + v: View, + ev: MotionEvent, + viewInitialX: Float, + viewInitialY: Float, + dx: Float, + dy: Float + ) + + /** + * Called when an ACTION_UP event is received for the given view. This signals that a drag or + * fling gesture has completed. + * + * @param viewInitialX The view's translationX value when this touch gesture started. + * @param viewInitialY The view's translationY value when this touch gesture started. + * @param dx Horizontal distance covered, in pixels. + * @param dy Vertical distance covered, in pixels. + * @param velX The final horizontal velocity of the gesture, in pixels/second. + * @param velY The final vertical velocity of the gesture, in pixels/second. + */ + abstract fun onUp( + v: View, + ev: MotionEvent, + viewInitialX: Float, + viewInitialY: Float, + dx: Float, + dy: Float, + velX: Float, + velY: Float + ) + + /** The raw coordinates of the last ACTION_DOWN event. */ + private val touchDown = PointF() + + /** The coordinates of the view, at the time of the last ACTION_DOWN event. */ + private val viewPositionOnTouchDown = PointF() + + private val velocityTracker = VelocityTracker.obtain() + + private var touchSlop: Int = -1 + private var movedEnough = false + + private val handler = Handler() + private var performedLongClick = false + + @Suppress("UNCHECKED_CAST") + override fun onTouch(v: View, ev: MotionEvent): Boolean { + addMovement(ev) + + val dx = ev.rawX - touchDown.x + val dy = ev.rawY - touchDown.y + + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + if (!onDown(v, ev)) { + return false + } + + // Grab the touch slop, it might have changed if the config changed since the + // last gesture. + touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop + + touchDown.set(ev.rawX, ev.rawY) + viewPositionOnTouchDown.set(v.translationX, v.translationY) + + performedLongClick = false + handler.postDelayed({ + performedLongClick = v.performLongClick() + }, ViewConfiguration.getLongPressTimeout().toLong()) + } + + MotionEvent.ACTION_MOVE -> { + if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) { + movedEnough = true + handler.removeCallbacksAndMessages(null) + } + + if (movedEnough) { + onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy) + } + } + + MotionEvent.ACTION_UP -> { + if (movedEnough) { + velocityTracker.computeCurrentVelocity(1000 /* units */) + onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy, + velocityTracker.xVelocity, velocityTracker.yVelocity) + } else if (!performedLongClick) { + v.performClick() + } else { + handler.removeCallbacksAndMessages(null) + } + + velocityTracker.clear() + movedEnough = false + } + } + + return true + } + + /** + * Adds a movement to the velocity tracker using raw screen coordinates. + */ + private fun addMovement(event: MotionEvent) { + val deltaX = event.rawX - event.x + val deltaY = event.rawY - event.y + event.offsetLocation(deltaX, deltaY) + velocityTracker.addMovement(event) + event.offsetLocation(-deltaX, -deltaY) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt new file mode 100644 index 000000000000..047298e6ed81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import androidx.lifecycle.LiveData + +interface RingerModeTracker { + + val ringerMode: LiveData<Int> + val ringerModeInternal: LiveData<Int> +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt new file mode 100644 index 000000000000..58684c386995 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioManager +import android.os.UserHandle +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RingerModeTrackerImpl @Inject constructor( + audioManager: AudioManager, + broadcastDispatcher: BroadcastDispatcher, + @Background executor: Executor +) : RingerModeTracker { + + override val ringerMode: LiveData<Int> = RingerModeLiveData( + broadcastDispatcher, + executor, + AudioManager.RINGER_MODE_CHANGED_ACTION, + audioManager::getRingerMode + ) + + override val ringerModeInternal: LiveData<Int> = RingerModeLiveData( + broadcastDispatcher, + executor, + AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, + audioManager::getRingerModeInternal + ) +} + +class RingerModeLiveData( + private val broadcastDispatcher: BroadcastDispatcher, + private val executor: Executor, + intent: String, + private val getter: () -> Int +) : MutableLiveData<Int>() { + + private val filter = IntentFilter(intent) + var initialSticky: Boolean = false + private set + + private val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + initialSticky = isInitialStickyBroadcast + postValue(intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1)) + } + } + + override fun getValue(): Int { + return super.getValue() ?: -1 + } + + override fun onActive() { + super.onActive() + broadcastDispatcher.registerReceiver(receiver, filter, executor, UserHandle.ALL) + executor.execute { + postValue(getter()) + } + } + + override fun onInactive() { + super.onInactive() + broadcastDispatcher.unregisterReceiver(receiver) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 812a1e4bc121..f27bdbfbeda0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -160,6 +160,18 @@ abstract class MagnetizedObject<T : Any>( lateinit var magnetListener: MagnetizedObject.MagnetListener /** + * Optional update listener to provide to the PhysicsAnimator that is used to spring the object + * into the target. + */ + var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null + + /** + * Optional end listener to provide to the PhysicsAnimator that is used to spring the object + * into the target. + */ + var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null + + /** * Sets whether forcefully flinging the object vertically towards a target causes it to be * attracted to the target and then released immediately, despite never being dragged within the * magnetic field. @@ -479,6 +491,14 @@ abstract class MagnetizedObject<T : Any>( .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY, springConfig) + if (physicsAnimatorUpdateListener != null) { + animator.addUpdateListener(physicsAnimatorUpdateListener!!) + } + + if (physicsAnimatorEndListener != null) { + animator.addEndListener(physicsAnimatorEndListener!!) + } + if (after != null) { animator.withEndActions(after) } @@ -560,13 +580,15 @@ abstract class MagnetizedObject<T : Any>( private val tempLoc = IntArray(2) fun updateLocationOnScreen() { - targetView.getLocationOnScreen(tempLoc) - - // Add half of the target size to get the center, and subtract translation since the - // target could be animating in while we're doing this calculation. - centerOnScreen.set( - tempLoc[0] + targetView.width / 2f - targetView.translationX, - tempLoc[1] + targetView.height / 2f - targetView.translationY) + targetView.post { + targetView.getLocationOnScreen(tempLoc) + + // Add half of the target size to get the center, and subtract translation since the + // target could be animating in while we're doing this calculation. + centerOnScreen.set( + tempLoc[0] + targetView.width / 2f - targetView.translationX, + tempLoc[1] + targetView.height / 2f - targetView.translationY) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 73532632c875..f19c49cc123f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -55,6 +55,8 @@ import android.util.Log; import android.util.Slog; import android.view.accessibility.AccessibilityManager; +import androidx.lifecycle.Observer; + import com.android.internal.annotations.GuardedBy; import com.android.settingslib.volume.MediaSessions; import com.android.systemui.Dumpable; @@ -64,6 +66,8 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -122,6 +126,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final NotificationManager mNoMan; private final SettingObserver mObserver; private final Receiver mReceiver = new Receiver(); + private final RingerModeObservers mRingerModeObservers; private final MediaSessions mMediaSessions; protected C mCallbacks = new C(); private final State mState = new State(); @@ -145,7 +150,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Inject public VolumeDialogControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher, - Optional<Lazy<StatusBar>> statusBarOptionalLazy) { + Optional<Lazy<StatusBar>> statusBarOptionalLazy, RingerModeTracker ringerModeTracker) { mContext = context.getApplicationContext(); // TODO(b/150663459): remove this TV workaround once StatusBar is "unbound" on TVs if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { @@ -164,6 +169,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mObserver = new SettingObserver(mWorker); + mRingerModeObservers = new RingerModeObservers( + (RingerModeLiveData) ringerModeTracker.getRingerMode(), + (RingerModeLiveData) ringerModeTracker.getRingerModeInternal() + ); + mRingerModeObservers.init(); mBroadcastDispatcher = broadcastDispatcher; mObserver.init(); mReceiver.init(); @@ -246,6 +256,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mMediaSessions.destroy(); mObserver.destroy(); mReceiver.destroy(); + mRingerModeObservers.destroy(); mWorkerThread.quitSafely(); } @@ -528,7 +539,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa ss.name = STREAMS.get(stream); checkRoutedToBluetoothW(stream); } - updateRingerModeExternalW(mAudio.getRingerMode()); + // We are not destroyed so this is listening and has updated information + updateRingerModeExternalW(mRingerModeObservers.mRingerMode.getValue()); updateZenModeW(); updateZenConfig(); updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); @@ -575,7 +587,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted); } if (muted && isRinger(stream)) { - updateRingerModeInternalW(mAudio.getRingerModeInternal()); + updateRingerModeInternalW(mRingerModeObservers.mRingerModeInternal.getValue()); } return true; } @@ -964,6 +976,79 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } } + private final class RingerModeObservers { + + private final RingerModeLiveData mRingerMode; + private final RingerModeLiveData mRingerModeInternal; + + private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() { + @Override + public void onChanged(Integer value) { + mWorker.post(() -> { + final int rm = value; + if (mRingerMode.getInitialSticky()) { + mState.ringerModeExternal = rm; + } + if (D.BUG) { + Log.d(TAG, "onChange ringer_mode rm=" + + Util.ringerModeToString(rm)); + } + if (updateRingerModeExternalW(rm)) { + mCallbacks.onStateChanged(mState); + } + } + ); + } + }; + + private final Observer<Integer> mRingerModeInternalObserver = new Observer<Integer>() { + @Override + public void onChanged(Integer value) { + mWorker.post(() -> { + final int rm = value; + if (mRingerModeInternal.getInitialSticky()) { + mState.ringerModeInternal = rm; + } + if (D.BUG) { + Log.d(TAG, "onChange internal_ringer_mode rm=" + + Util.ringerModeToString(rm)); + } + if (updateRingerModeInternalW(rm)) { + mCallbacks.onStateChanged(mState); + } + } + ); + } + }; + + RingerModeObservers(RingerModeLiveData ringerMode, + RingerModeLiveData ringerModeInternal) { + mRingerMode = ringerMode; + mRingerModeInternal = ringerModeInternal; + } + + public void init() { + int initialValue = mRingerMode.getValue(); + if (initialValue != -1) { + // If it's not -1, set it to the initial value, if it's -1, it means that the + // tracker is not listening already and will obtain the sticky value. + mState.ringerModeExternal = initialValue; + } + mRingerMode.observeForever(mRingerModeObserver); + initialValue = mRingerModeInternal.getValue(); + if (initialValue != -1) { + // If it's not -1, set it to the initial value, if it's -1, it means that the + // tracker is not listening already and will obtain the sticky value. + mState.ringerModeInternal = initialValue; + } + mRingerModeInternal.observeForever(mRingerModeInternalObserver); + } + + public void destroy() { + mRingerMode.removeObserver(mRingerModeObserver); + mRingerModeInternal.removeObserver(mRingerModeInternalObserver); + } + } private final class SettingObserver extends ContentObserver { private final Uri ZEN_MODE_URI = @@ -1006,8 +1091,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa final IntentFilter filter = new IntentFilter(); filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); @@ -1042,18 +1125,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa + stream + " devices=" + devices + " oldDevices=" + oldDevices); changed = checkRoutedToBluetoothW(stream); changed |= onVolumeChangedW(stream, 0); - } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); - if (isInitialStickyBroadcast()) mState.ringerModeExternal = rm; - if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm=" - + Util.ringerModeToString(rm)); - changed = updateRingerModeExternalW(rm); - } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) { - final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); - if (isInitialStickyBroadcast()) mState.ringerModeInternal = rm; - if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm=" - + Util.ringerModeToString(rm)); - changed = updateRingerModeInternalW(rm); } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); final boolean muted = intent diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index bae5bb41aa5a..c22b718fa50f 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -272,13 +272,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } mAnimation.cancel(); } - mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; final float hiddenY = defaultY + imeSource.getFrame().height(); final float shownY = defaultY; final float startY = show ? hiddenY : shownY; final float endY = show ? shownY : hiddenY; + if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { + // IME is already showing, so set seek to end + seekValue = shownY; + seek = true; + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mImeShowing = show; mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java index 4652abfa0721..cfec1c07ff1d 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java @@ -27,6 +27,7 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.annotation.IntDef; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; @@ -46,16 +47,27 @@ import android.view.Surface; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Contains information about the layout-properties of a display. This refers to internal layout * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to * DisplayPolicy. */ public class DisplayLayout { + @IntDef(prefix = { "NAV_BAR_" }, value = { + NAV_BAR_LEFT, + NAV_BAR_RIGHT, + NAV_BAR_BOTTOM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavBarPosition {} + // Navigation bar position values - private static final int NAV_BAR_LEFT = 1 << 0; - private static final int NAV_BAR_RIGHT = 1 << 1; - private static final int NAV_BAR_BOTTOM = 1 << 2; + public static final int NAV_BAR_LEFT = 1 << 0; + public static final int NAV_BAR_RIGHT = 1 << 1; + public static final int NAV_BAR_BOTTOM = 1 << 2; private int mUiMode; private int mWidth; @@ -214,6 +226,14 @@ public class DisplayLayout { } /** + * Gets navigation bar position for this layout + * @return Navigation bar position for this layout. + */ + public @NavBarPosition int getNavigationBarPosition(Resources res) { + return navigationBarPosition(res, mWidth, mHeight, mRotation); + } + + /** * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and * remains at 0,0 after rotation. @@ -437,8 +457,8 @@ public class DisplayLayout { } /** Retrieve navigation bar position from resources based on rotation and size. */ - public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight, - int rotation) { + public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth, + int displayHeight, int rotation) { boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean( com.android.internal.R.bool.config_navBarCanMove); if (navBarCanMove && displayWidth > displayHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 381ccdb50386..e5da603321cd 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -194,7 +194,9 @@ public class SystemWindows { return; } final Display display = mDisplayController.getDisplay(mDisplayId); - SurfaceControlViewHost viewRoot = new SurfaceControlViewHost(mContext, display, wwm); + SurfaceControlViewHost viewRoot = + new SurfaceControlViewHost(mContext, display, wwm, + true /* useSfChoreographer */); attrs.flags |= FLAG_HARDWARE_ACCELERATED; viewRoot.setView(view, attrs); mViewRoots.put(view, viewRoot); @@ -256,12 +258,13 @@ public class SystemWindows { Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, cutout, mergedConfiguration, outSurfaceControl, outInsetsState, - outSurfaceSize, outBLASTSurfaceControl); + outActiveControls, outSurfaceSize, outBLASTSurfaceControl); if (res != 0) { return res; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index cf1299fc66e0..ce032c94deab 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -115,6 +115,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { @Test public void testShow_dismissedByCallback() throws Exception { + doAnswer(answerVoid(Runnable::run)).when(mHandler).post(any(Runnable.class)); doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; callback.onDismiss(); @@ -184,7 +185,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private void verifyViewDismissed(SurfaceView v) throws Exception { verify(mParent).removeView(v); - verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID); + verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true); assertThat(mContext.isBound(mComponentName)).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt index 072bc446fd21..4bcf917fa95d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt @@ -16,8 +16,12 @@ package com.android.keyguard +import android.app.Notification import android.graphics.drawable.Icon import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.media.MediaControllerFactory import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -38,6 +44,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever public class KeyguardMediaPlayerTest : SysuiTestCase() { private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer + @Mock private lateinit var mockMediaFactory: MediaControllerFactory + @Mock private lateinit var mockMediaController: MediaController + private lateinit var playbackState: PlaybackState private lateinit var fakeExecutor: FakeExecutor private lateinit var mediaMetadata: MediaMetadata.Builder - private lateinit var entry: NotificationEntryBuilder + private lateinit var entry: NotificationEntry @Mock private lateinit var mockView: View private lateinit var songView: TextView private lateinit var artistView: TextView @@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Before public fun setup() { + playbackState = PlaybackState.Builder().run { + build() + } + mockMediaController = mock(MediaController::class.java) + whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState) + mockMediaFactory = mock(MediaControllerFactory::class.java) + whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController) + fakeExecutor = FakeExecutor(FakeSystemClock()) - keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor) + keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor) mockIcon = mock(Icon::class.java) mockView = mock(View::class.java) @@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView) mediaMetadata = MediaMetadata.Builder() - entry = NotificationEntryBuilder() + entry = NotificationEntryBuilder().build() + entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, + MediaSession.Token(1, null)) ArchTaskExecutor.getInstance().setDelegate(taskExecutor) @@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Test public fun testUpdateControls() { - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) verify(mockView).setVisibility(View.VISIBLE) } @@ -122,11 +142,22 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { } @Test + public fun testUpdateControlsNullPlaybackState() { + // GIVEN that the playback state is null (ie. the media session was destroyed) + whenever(mockMediaController.getPlaybackState()).thenReturn(null) + // WHEN updated + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) + FakeExecutor.exhaustExecutors(fakeExecutor) + // THEN the controls are cleared (ie. visibility is set to GONE) + verify(mockView).setVisibility(View.GONE) + } + + @Test public fun testSongName() { val song: String = "Song" mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song) - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(songView.getText()).isEqualTo(song) @@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { val artist: String = "Artist" mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist) - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(artistView.getText()).isEqualTo(artist) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index d47fceea9724..64590fd3a36b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -25,6 +25,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +37,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @UiThreadTest @Test public void showSecurityScreen_canInflateAllModes() { + mDependency.injectMockDependency(KeyguardStateController.class); KeyguardSecurityContainer keyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 73f9d8ad6953..7403a11fecbf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -29,6 +29,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -50,6 +52,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; @@ -63,18 +66,24 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; + import com.android.internal.telephony.TelephonyIntents; import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.util.RingerModeTracker; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -125,6 +134,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private Executor mBackgroundExecutor; + @Mock + private RingerModeTracker mRingerModeTracker; + @Mock + private LiveData<Integer> mRingerModeLiveData; private TestableLooper mTestableLooper; private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -156,6 +169,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager); + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context); @@ -613,6 +628,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.getSecondaryLockscreenRequirement(user)).isNull(); } + @Test + public void testRingerModeChange() { + ArgumentCaptor<Observer<Integer>> captor = ArgumentCaptor.forClass(Observer.class); + verify(mRingerModeLiveData).observeForever(captor.capture()); + Observer<Integer> observer = captor.getValue(); + + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + + mKeyguardUpdateMonitor.registerCallback(callback); + + observer.onChanged(AudioManager.RINGER_MODE_NORMAL); + observer.onChanged(AudioManager.RINGER_MODE_SILENT); + observer.onChanged(AudioManager.RINGER_MODE_VIBRATE); + + mTestableLooper.processAllMessages(); + + InOrder orderVerify = inOrder(callback); + orderVerify.verify(callback).onRingerModeChanged(anyInt()); // Initial update on register + orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_NORMAL); + orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_SILENT); + orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_VIBRATE); + } + private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) { BroadcastReceiver.PendingResult pendingResult = new BroadcastReceiver.PendingResult(Activity.RESULT_OK, @@ -643,7 +681,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected TestableKeyguardUpdateMonitor(Context context) { super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), - mBroadcastDispatcher, mDumpManager, mBackgroundExecutor); + mBroadcastDispatcher, mDumpManager, + mRingerModeTracker, mBackgroundExecutor); mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 69e933e95562..45f9437efc15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.util.time.FakeSystemClock; import junit.framework.Assert; @@ -69,6 +70,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { private ForegroundServiceController mFsc; private ForegroundServiceNotificationListener mListener; private NotificationEntryListener mEntryListener; + private final FakeSystemClock mClock = new FakeSystemClock(); @Mock private NotificationEntryManager mEntryManager; @Mock private AppOpsController mAppOpsController; @Mock private Handler mMainHandler; @@ -80,9 +82,10 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { allowTestableLooperAsMainThread(); MockitoAnnotations.initMocks(this); - mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler); + mFsc = new ForegroundServiceController( + mEntryManager, mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( - mContext, mFsc, mEntryManager, mNotifPipeline); + mContext, mFsc, mEntryManager, mNotifPipeline, mClock); ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = ArgumentCaptor.forClass(NotificationEntryListener.class); verify(mEntryManager).addNotificationEntryListener( diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java index 46a473bd6543..bca8deeafd34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java @@ -17,7 +17,6 @@ package com.android.systemui; import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS; -import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -29,6 +28,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -37,12 +37,15 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class ForegroundServiceNotificationListenerTest extends SysuiTestCase { - private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender(); + private ForegroundServiceLifetimeExtender mExtender; private NotificationEntry mEntry; private Notification mNotif; + private final FakeSystemClock mClock = new FakeSystemClock(); @Before public void setup() { + mExtender = new ForegroundServiceLifetimeExtender(mClock); + mNotif = new Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") @@ -50,6 +53,7 @@ public class ForegroundServiceNotificationListenerTest extends SysuiTestCase { .build(); mEntry = new NotificationEntryBuilder() + .setCreationTime(mClock.uptimeMillis()) .setNotification(mNotif) .build(); } @@ -62,27 +66,26 @@ public class ForegroundServiceNotificationListenerTest extends SysuiTestCase { // Extend the lifetime of a FGS notification iff it has not been visible // for the minimum time mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; - modifySbn(mEntry) - .setPostTime(System.currentTimeMillis()) - .build(); + + // No time has elapsed, keep showing assertTrue(mExtender.shouldExtendLifetime(mEntry)); } @Test public void testShouldExtendLifetime_shouldNot_foreground() { mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; - modifySbn(mEntry) - .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1) - .build(); + + // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1 + mClock.advanceTime(MIN_FGS_TIME_MS + 1); assertFalse(mExtender.shouldExtendLifetime(mEntry)); } @Test public void testShouldExtendLifetime_shouldNot_notForeground() { mNotif.flags = 0; - modifySbn(mEntry) - .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1) - .build(); + + // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1 + mClock.advanceTime(MIN_FGS_TIME_MS + 1); assertFalse(mExtender.shouldExtendLifetime(mEntry)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 475023e2506d..5227aaf01249 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -160,7 +160,7 @@ public class ImageWallpaperTest extends SysuiTestCase { LOW_BMP_HEIGHT /* bmpHeight */, LOW_BMP_WIDTH /* surfaceWidth */, LOW_BMP_HEIGHT /* surfaceHeight */, - true /* assertion */); + false /* assertion */); } @Test @@ -172,7 +172,7 @@ public class ImageWallpaperTest extends SysuiTestCase { INVALID_BMP_HEIGHT /* bmpHeight */, ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */, ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */, - true /* assertion */); + false /* assertion */); } private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight, diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index 32604f8609fa..b2c35867e789 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -38,6 +38,8 @@ import com.android.systemui.broadcast.FakeBroadcastDispatcher; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; import org.junit.After; import org.junit.Before; @@ -97,6 +99,12 @@ public abstract class SysuiTestCase { // A lot of tests get the LocalBluetoothManager, often via several layers of indirection. // None of them actually need it. mDependency.injectMockDependency(LocalBluetoothManager.class); + + // Notifications tests are injecting one of these, causing many classes (including + // KeyguardUpdateMonitor to be created (injected). + // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this + mDependency.injectMockDependency(SmartReplyController.class); + mDependency.injectMockDependency(NotificationBlockingHelperManager.class); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 037f04ec1d7c..e472de349466 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -374,7 +374,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); assertTrue(mBubbleController.hasBubbles()); - mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); assertFalse(mNotificationShadeWindowController.getBubblesShowing()); verify(mNotificationEntryManager, times(3)).updateNotifications(any()); assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); @@ -399,7 +399,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand the stack BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); assertTrue(mNotificationShadeWindowController.getBubbleExpanded()); @@ -436,7 +436,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -448,7 +448,7 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry())); // Switch which bubble is expanded - mBubbleController.selectBubble(mRow.getEntry().getKey()); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); assertEquals(mRow.getEntry(), mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry()); @@ -482,7 +482,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); @@ -510,7 +510,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); @@ -544,7 +544,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mSysUiStateBubblesExpanded); @@ -726,7 +726,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); - mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(2)).send(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 545de210d5b5..5f4f2ef04c1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -321,7 +321,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); assertTrue(mBubbleController.hasBubbles()); - mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); assertFalse(mNotificationShadeWindowController.getBubblesShowing()); verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); @@ -344,7 +344,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Expand the stack BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); assertTrue(mNotificationShadeWindowController.getBubbleExpanded()); @@ -376,7 +376,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -385,7 +385,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); // Switch which bubble is expanded - mBubbleController.selectBubble(mRow.getEntry().getKey()); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); assertEquals(mRow.getEntry(), mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry()); @@ -416,7 +416,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); @@ -442,7 +442,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); @@ -474,7 +474,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -628,7 +628,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); - mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(2)).send(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index 88316f2d4323..bb003ee59978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -125,7 +125,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { loadSubscriberCaptor.value.onSubscribe(Binder(), subscription) canceller.run() - verify(subscription).cancel() + verify(providers[0]).cancelSubscription(subscription) } @Test @@ -145,7 +145,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { loadSubscriberCaptor.value.onComplete(b) canceller.run() - verify(subscription, never()).cancel() + verify(providers[0], never()).cancelSubscription(subscription) } @Test @@ -203,7 +203,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { loadSubscriberCaptor.value.onError(b, "") canceller.run() - verify(subscription, never()).cancel() + verify(providers[0], never()).cancelSubscription(subscription) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java index c9bb4016c7bf..9985d21e8515 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java @@ -20,8 +20,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.hardware.display.AmbientDisplayConfiguration; import android.testing.AndroidTestingRunner; @@ -56,6 +58,7 @@ public class DozeDockHandlerTest extends SysuiTestCase { mDockManagerFake = spy(new DockManagerFake()); mDockHandler = new DozeDockHandler(mConfig, mMachine, mDockManagerFake); + when(mMachine.getState()).thenReturn(State.DOZE_AOD); doReturn(true).when(mConfig).alwaysOnEnabled(anyInt()); mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED); } @@ -101,4 +104,31 @@ public class DozeDockHandlerTest extends SysuiTestCase { verify(mMachine).requestState(eq(State.DOZE)); } + + @Test + public void onEvent_dockedWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED); + + verify(mMachine, never()).requestState(any(State.class)); + } + + @Test + public void onEvent_noneWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_NONE); + + verify(mMachine, never()).requestState(any(State.class)); + } + + @Test + public void onEvent_hideWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE); + + verify(mMachine, never()).requestState(any(State.class)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index c483314918fc..1f07f46bf764 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -254,6 +254,17 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_whileDockedAoD_staysDockedAod() { + when(mDockManager.isDocked()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestState(DOZE_AOD_DOCKED); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock, never()).transitionTo(DOZE_AOD_DOCKED, DOZE_PULSE_DONE); + } + + @Test public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() { when(mHost.isDozeSuppressed()).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 137a126f539d..300cb212ffb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -18,6 +18,7 @@ package com.android.systemui.globalactions; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; @@ -52,6 +53,8 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; import org.junit.Before; import org.junit.Test; @@ -95,6 +98,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private ControlsListingController mControlsListingController; @Mock private ControlsController mControlsController; @Mock private UiEventLogger mUiEventLogger; + @Mock private RingerModeTracker mRingerModeTracker; + @Mock private RingerModeLiveData mRingerModeLiveData; private TestableLooper mTestableLooper; @@ -103,6 +108,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); + + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); mGlobalActionsDialog = new GlobalActionsDialog(mContext, mWindowManagerFuncs, mAudioManager, @@ -133,7 +140,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mBackgroundExecutor, mControlsListingController, mControlsController, - mUiEventLogger + mUiEventLogger, + mRingerModeTracker ); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 260f52070a70..d407b8a1e449 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -67,10 +67,11 @@ public class SeekBarObserverTest : SysuiTestCase() { val isEnabled = false val data = SeekBarViewModel.Progress(isEnabled, false, null, null, null) observer.onChanged(data) - // THEN seek bar visibility is set to GONE - assertThat(seekBarView.getVisibility()).isEqualTo(View.GONE) - assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.GONE) - assertThat(totalTimeView.getVisibility()).isEqualTo(View.GONE) + // THEN seek bar shows just a line with no text + assertThat(seekBarView.isEnabled()).isFalse() + assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0) + assertThat(elapsedTimeView.getText()).isEqualTo("") + assertThat(totalTimeView.getText()).isEqualTo("") } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java index 0bf0f04d2d43..425bf88ebec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.pip; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.ComponentName; @@ -56,11 +57,15 @@ public class PipBoundsHandlerTest extends SysuiTestCase { private PipBoundsHandler mPipBoundsHandler; private DisplayInfo mDefaultDisplayInfo; + private ComponentName mTestComponentName1; + private ComponentName mTestComponentName2; @Before public void setUp() throws Exception { initializeMockResources(); mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext)); + mTestComponentName1 = new ComponentName(mContext, "component1"); + mTestComponentName2 = new ComponentName(mContext, "component2"); mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo); } @@ -121,7 +126,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; for (float aspectRatio : aspectRatios) { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); assertEquals("Destination bounds matches the given aspect ratio", @@ -137,7 +142,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; for (float aspectRatio : invalidAspectRatios) { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); assertEquals("Destination bounds fallbacks to default aspect ratio", @@ -153,7 +158,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left; final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); @@ -177,7 +182,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { final float aspectRatio = aspectRatios[i]; final Size minimalSize = minimalSizes[i]; final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize); assertTrue("Destination bounds is no smaller than minimal requirement", (destinationBounds.width() == minimalSize.getWidth() && destinationBounds.height() >= minimalSize.getHeight()) @@ -198,7 +203,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2); final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, currentBounds, minSize); + mTestComponentName1, aspectRatio, currentBounds, minSize); assertTrue("Destination bounds ignores minimal size", destinationBounds.width() > minSize.getWidth() @@ -206,81 +211,92 @@ public class PipBoundsHandlerTest extends SysuiTestCase { } @Test + public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() { + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, + DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + + oldPosition.offset(0, -100); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition); + + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2, + DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + + assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition); + } + + @Test public void setShelfHeight_offsetBounds() { final int shelfHeight = 100; - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); mPipBoundsHandler.setShelfHeight(true, shelfHeight); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -shelfHeight); - assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition); + assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition); } @Test public void onImeVisibilityChanged_offsetBounds() { final int imeHeight = 100; - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -imeHeight); - assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition); + assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition); } @Test public void onSaveReentryBounds_restoreLastPosition() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition); + assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition); } @Test public void onResetReentryBounds_useDefaultBounds() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds( + final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final Rect newBounds = new Rect(defaultBounds); newBounds.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds); - mPipBoundsHandler.onResetReentryBounds(componentName); - final Rect actualBounds = mPipBoundsHandler.getDestinationBounds( + mPipBoundsHandler.onResetReentryBounds(mTestComponentName1); + final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds); + assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds); } @Test public void onResetReentryBounds_componentMismatch_restoreLastPosition() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds( + final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final Rect newBounds = new Rect(defaultBounds); newBounds.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds); - mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2")); - final Rect actualBounds = mPipBoundsHandler.getDestinationBounds( + mPipBoundsHandler.onResetReentryBounds(mTestComponentName2); + final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds); + assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds); } - private void assertBoundsWithMargin(String from, Rect expected, Rect actual) { + private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) { final Rect expectedWithMargin = new Rect(expected); expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); assertTrue(from + ": expect " + expected @@ -288,4 +304,13 @@ public class PipBoundsHandlerTest extends SysuiTestCase { + " with error margin " + ROUNDING_ERROR_MARGIN, expectedWithMargin.contains(actual)); } + + private void assertNonBoundsInclusionWithMargin(String from, Rect expected, Rect actual) { + final Rect expectedWithMargin = new Rect(expected); + expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); + assertFalse(from + ": expect " + expected + + " not contains " + actual + + " with error margin " + ROUNDING_ERROR_MARGIN, + expectedWithMargin.contains(actual)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 6b7a3bfce5ad..c874915e9124 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -117,12 +117,27 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } @Test - fun updateGlobalDialogVisibility_appliesBlur() { + fun updateGlobalDialogVisibility_animatesBlur() { notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root) verify(globalActionsSpring).animateTo(eq(maxBlur / 2), safeEq(root)) } @Test + fun updateGlobalDialogVisibility_appliesBlur_withoutHomeControls() { + `when`(globalActionsSpring.radius).thenReturn(maxBlur) + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(maxBlur)) + } + + @Test + fun updateGlobalDialogVisibility_appliesBlur_unlessHomeControls() { + notificationShadeDepthController.showingHomeControls = true + `when`(globalActionsSpring.radius).thenReturn(maxBlur) + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(0)) + } + + @Test fun updateBlurCallback_setsBlurAndZoom() { notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index 43cf83f380f1..81f9546ba885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -29,6 +29,7 @@ import android.service.notification.StatusBarNotification; import com.android.internal.logging.InstanceId; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.util.time.FakeSystemClock; import java.util.ArrayList; @@ -44,16 +45,22 @@ import java.util.ArrayList; public class NotificationEntryBuilder { private final SbnBuilder mSbnBuilder = new SbnBuilder(); private final RankingBuilder mRankingBuilder = new RankingBuilder(); + private final FakeSystemClock mClock = new FakeSystemClock(); private StatusBarNotification mSbn = null; /* ListEntry properties */ private GroupEntry mParent; private int mSection = -1; + /* If set, use this creation time instead of mClock.uptimeMillis */ + private long mCreationTime = -1; + public NotificationEntry build() { StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build(); mRankingBuilder.setKey(sbn.getKey()); - final NotificationEntry entry = new NotificationEntry(sbn, mRankingBuilder.build()); + long creationTime = mCreationTime != -1 ? mCreationTime : mClock.uptimeMillis(); + final NotificationEntry entry = new NotificationEntry( + sbn, mRankingBuilder.build(), mClock.uptimeMillis()); /* ListEntry properties */ entry.setParent(mParent); @@ -86,6 +93,14 @@ public class NotificationEntryBuilder { return this; } + /** + * Set the creation time + */ + public NotificationEntryBuilder setCreationTime(long creationTime) { + mCreationTime = creationTime; + return this; + } + /* Delegated to SbnBuilder */ public NotificationEntryBuilder setPkg(String pkg) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 5b0b66849027..1a022ec7c87d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -52,6 +52,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -71,6 +72,7 @@ public class NotificationEntryTest extends SysuiTestCase { private int mId; private NotificationEntry mEntry; + private final FakeSystemClock mClock = new FakeSystemClock(); @Before public void setup() { @@ -187,7 +189,7 @@ public class NotificationEntryTest extends SysuiTestCase { .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking); + new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); assertEquals(systemGeneratedSmartActions, entry.getSmartActions()); assertEquals(NOTIFICATION_CHANNEL, entry.getChannel()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index be026f7884c3..1c6e5a36bd8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.row; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + import static junit.framework.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; @@ -139,6 +141,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { private NotificationRowBinderImpl mRowBinder; private Handler mHandler; private FakeExecutor mBgExecutor; + private RowContentBindStage mRowContentBindStage; @Before public void setUp() { @@ -147,10 +150,13 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mHandler = Handler.createAsync(TestableLooper.get(this).getLooper()); + // Add an action so heads up content views are made + Notification.Action action = new Notification.Action.Builder(null, null, null).build(); Notification notification = new Notification.Builder(mContext) .setSmallIcon(R.drawable.ic_person) .setContentTitle(TEST_TITLE) .setContentText(TEST_TEXT) + .setActions(action) .build(); mSbn = new SbnBuilder() .setNotification(notification) @@ -192,11 +198,11 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { () -> mock(SmartReplyController.class), mock(ConversationNotificationProcessor.class), mBgExecutor); - RowContentBindStage stage = new RowContentBindStage( + mRowContentBindStage = new RowContentBindStage( binder, mock(NotifInflationErrorManager.class), mock(RowContentBindStageLogger.class)); - pipeline.setStage(stage); + pipeline.setStage(mRowContentBindStage); ArgumentCaptor<ExpandableNotificationRow> viewCaptor = ArgumentCaptor.forClass(ExpandableNotificationRow.class); @@ -232,7 +238,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { "FOOBAR", "FOOBAR", mKeyguardBypassController, mGroupManager, - stage, + mRowContentBindStage, mock(NotificationLogger.class), mHeadsUpManager, mPresenter, @@ -256,7 +262,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mRemoteInputManager, mLockscreenUserManager, pipeline, - stage, + mRowContentBindStage, mNotificationInterruptionStateProvider, RowInflaterTask::new, mExpandableNotificationRowComponentBuilder, @@ -365,6 +371,27 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { verify(mPresenter).updateNotificationViews(); } + @Test + public void testContentViewInflationDuringRowInflationInflatesCorrectViews() { + // GIVEN a notification is added and the row is inflating + mEntryManager.addNotification(mSbn, mRankingMap); + ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass( + NotificationEntry.class); + verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture()); + NotificationEntry entry = entryCaptor.getValue(); + + // WHEN we try to bind a content view + mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + mRowContentBindStage.requestRebind(entry, null); + + waitForInflation(); + + // THEN the notification has its row and all relevant content views inflated + assertNotNull(entry.getRow()); + assertNotNull(entry.getRow().getPrivateLayout().getContractedChild()); + assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild()); + } + /** * Wait for inflation to finish. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index 65fbe79b5e9f..0a10ab2fbf02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -16,29 +16,43 @@ package com.android.systemui.toast; +import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; +import static android.widget.ToastPresenter.TEXT_TOAST_LAYOUT; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.app.ITransientNotificationCallback; +import android.content.Context; import android.os.Binder; +import android.os.Parcel; +import android.os.Parcelable; import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import android.widget.ToastPresenter; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.util.IntPair; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.CommandQueue; @@ -49,32 +63,53 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; @SmallTest @RunWith(AndroidTestingRunner.class) public class ToastUITest extends SysuiTestCase { + private static final int ANDROID_UID = 1000; + private static final int SYSTEMUI_UID = 10140; + + private static final int UID_1 = 10255; private static final String PACKAGE_NAME_1 = "com.example1.test"; private static final Binder TOKEN_1 = new Binder(); private static final Binder WINDOW_TOKEN_1 = new Binder(); + + private static final int UID_2 = 10256; private static final String PACKAGE_NAME_2 = "com.example2.test"; private static final Binder TOKEN_2 = new Binder(); private static final Binder WINDOW_TOKEN_2 = new Binder(); + private static final String TEXT = "Hello World"; private static final int MESSAGE_RES_ID = R.id.message; + private Context mContextSpy; + private ToastUI mToastUI; + @Mock private LayoutInflater mLayoutInflater; @Mock private CommandQueue mCommandQueue; @Mock private WindowManager mWindowManager; @Mock private INotificationManager mNotificationManager; - @Mock private AccessibilityManager mAccessibilityManager; + @Mock private IAccessibilityManager mAccessibilityManager; @Mock private ITransientNotificationCallback mCallback; @Captor private ArgumentCaptor<View> mViewCaptor; @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor; - private ToastUI mToastUI; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mToastUI = new ToastUI(mContext, mCommandQueue, mWindowManager, mNotificationManager, + + // This is because inflate will result in WindowManager (WM) calls, which will fail since we + // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM. + View view = ToastPresenter.getTextToastView(mContext, TEXT); + when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view); + mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater); + + mContext.addMockSystemService(WindowManager.class, mWindowManager); + mContextSpy = spy(mContext); + doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt()); + + mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager, mAccessibilityManager); } @@ -87,7 +122,8 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_addsCorrectViewToWindowManager() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null); + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + null); verify(mWindowManager).addView(mViewCaptor.capture(), any()); View view = mViewCaptor.getValue(); @@ -96,13 +132,14 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null); + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + null); verify(mWindowManager).addView(any(), mParamsCaptor.capture()); ViewGroup.LayoutParams params = mParamsCaptor.getValue(); assertThat(params).isInstanceOf(WindowManager.LayoutParams.class); WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params; - assertThat(windowParams.packageName).isEqualTo(mContext.getPackageName()); + assertThat(windowParams.packageName).isEqualTo(mContextSpy.getPackageName()); assertThat(windowParams.getTitle()).isEqualTo("Toast"); assertThat(windowParams.token).isEqualTo(WINDOW_TOKEN_1); assertThat(windowParams.privateFlags @@ -111,7 +148,8 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_forAndroidPackage_addsAllUserFlag() throws Exception { - mToastUI.showToast("android", TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null); + mToastUI.showToast(ANDROID_UID, "android", TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + null); verify(mWindowManager).addView(any(), mParamsCaptor.capture()); ViewGroup.LayoutParams params = mParamsCaptor.getValue(); @@ -123,8 +161,8 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_forSystemUiPackage_addsAllUserFlag() throws Exception { - mToastUI.showToast("com.android.systemui", TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, - null); + mToastUI.showToast(SYSTEMUI_UID, "com.android.systemui", TOKEN_1, TEXT, WINDOW_TOKEN_1, + Toast.LENGTH_LONG, null); verify(mWindowManager).addView(any(), mParamsCaptor.capture()); ViewGroup.LayoutParams params = mParamsCaptor.getValue(); @@ -136,7 +174,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_callsCallback() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); verify(mCallback).onToastShown(); @@ -144,14 +182,24 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_sendsAccessibilityEvent() throws Exception { - when(mAccessibilityManager.isEnabled()).thenReturn(true); - - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null); + // Enable accessibility + when(mAccessibilityManager.addClient(any(), anyInt())).thenReturn( + IntPair.of(STATE_FLAG_ACCESSIBILITY_ENABLED, AccessibilityEvent.TYPES_ALL_MASK)); + // AccessibilityManager recycles the event that goes over the wire after making the binder + // call to the service. Since we are mocking the service, that call is local, so if we use + // ArgumentCaptor or ArgumentMatcher it will retain a reference to the recycled event, which + // will already have its state reset by the time we verify its contents. So, instead, we + // serialize it at call-time and later on deserialize it to verity its contents. + Parcel eventParcel = Parcel.obtain(); + doAnswer(writeArgumentToParcel(0, eventParcel)).when( + mAccessibilityManager).sendAccessibilityEvent(any(), anyInt()); + + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + null); - ArgumentCaptor<AccessibilityEvent> eventCaptor = ArgumentCaptor.forClass( - AccessibilityEvent.class); - verify(mAccessibilityManager).sendAccessibilityEvent(eventCaptor.capture()); - AccessibilityEvent event = eventCaptor.getValue(); + eventParcel.setDataPosition(0); + assertThat(eventParcel.dataSize()).isGreaterThan(0); + AccessibilityEvent event = AccessibilityEvent.CREATOR.createFromParcel(eventParcel); assertThat(event.getEventType()).isEqualTo( AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); assertThat(event.getClassName()).isEqualTo(Toast.class.getName()); @@ -160,7 +208,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testHideToast_removesView() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); View view = verifyWmAddViewAndAttachToParent(); @@ -171,7 +219,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testHideToast_finishesToken() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1); @@ -181,7 +229,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testHideToast_callsCallback() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1); @@ -191,7 +239,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2); @@ -201,7 +249,7 @@ public class ToastUITest extends SysuiTestCase { @Test public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1); @@ -211,11 +259,12 @@ public class ToastUITest extends SysuiTestCase { @Test public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception { - mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback); View view = verifyWmAddViewAndAttachToParent(); - mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null); + mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, + null); verify(mWindowManager).removeViewImmediate(view); verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1); @@ -227,8 +276,15 @@ public class ToastUITest extends SysuiTestCase { verify(mWindowManager).addView(viewCaptor.capture(), any()); View view = viewCaptor.getValue(); // Simulate attaching to view hierarchy - ViewGroup parent = new FrameLayout(mContext); + ViewGroup parent = new FrameLayout(mContextSpy); parent.addView(view); return view; } + + private Answer<Void> writeArgumentToParcel(int i, Parcel dest) { + return inv -> { + inv.<Parcelable>getArgument(i).writeToParcel(dest, 0); + return null; + }; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt new file mode 100644 index 000000000000..2489c301f6f5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.BroadcastReceiver +import android.content.IntentFilter +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.lifecycle.Observer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class RingerModeLiveDataTest : SysuiTestCase() { + + companion object { + private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + private fun <T> any(): T = Mockito.any() + private fun <T> eq(value: T): T = Mockito.eq(value) ?: value + private val INTENT = "INTENT" + } + + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var valueSupplier: () -> Int + @Mock + private lateinit var observer: Observer<Int> + @Captor + private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> + @Captor + private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter> + + // Run everything immediately + private val executor = Executor { it.run() } + private lateinit var liveData: RingerModeLiveData + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + liveData = RingerModeLiveData(broadcastDispatcher, executor, INTENT, valueSupplier) + } + + @After + fun tearDown() { + liveData.removeObserver(observer) + } + + @Test + fun testInit_broadcastNotRegistered() { + verifyNoMoreInteractions(broadcastDispatcher) + } + + @Test + fun testOnActive_broadcastRegistered() { + liveData.observeForever(observer) + verify(broadcastDispatcher).registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL)) + } + + @Test + fun testOnActive_intentFilterHasIntent() { + liveData.observeForever(observer) + verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(), + any()) + assertTrue(intentFilterCaptor.value.hasAction(INTENT)) + } + + @Test + fun testOnActive_valueObtained() { + liveData.observeForever(observer) + verify(valueSupplier).invoke() + } + + @Test + fun testOnInactive_broadcastUnregistered() { + liveData.observeForever(observer) + liveData.removeObserver(observer) + verify(broadcastDispatcher).unregisterReceiver(any()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt index f1672b1c644d..f6b7b74d4bfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt @@ -106,6 +106,10 @@ class MagnetizedObjectTest : SysuiTestCase() { location[1] = targetCenterY - targetSize / 2 // y = 800 } }.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any()) + doAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + true + }.`when`(targetView).post(ArgumentMatchers.any()) `when`(targetView.context).thenReturn(context) magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius) @@ -408,6 +412,10 @@ class MagnetizedObjectTest : SysuiTestCase() { `when`(secondTargetView.width).thenReturn(targetSize) // width = 200 `when`(secondTargetView.height).thenReturn(targetSize) // height = 200 doAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + true + }.`when`(secondTargetView).post(ArgumentMatchers.any()) + doAnswer { invocation -> (invocation.arguments[0] as IntArray).also { location -> // Return the top left of the target. location[0] = secondTargetCenterX - targetSize / 2 // x = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 8cc83dd99b53..6166cd76e005 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -31,6 +31,7 @@ import android.media.session.MediaSession; import android.os.Handler; import android.os.Process; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.test.filters.SmallTest; @@ -38,7 +39,10 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,6 +53,7 @@ import java.util.Optional; @RunWith(AndroidTestingRunner.class) @SmallTest +@TestableLooper.RunWithLooper public class VolumeDialogControllerImplTest extends SysuiTestCase { TestableVolumeDialogControllerImpl mVolumeController; @@ -56,18 +61,35 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { StatusBar mStatusBar; @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private RingerModeTracker mRingerModeTracker; + @Mock + private RingerModeLiveData mRingerModeLiveData; + @Mock + private RingerModeLiveData mRingerModeInternalLiveData; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mRingerModeTracker.getRingerModeInternal()).thenReturn(mRingerModeInternalLiveData); + // Initial non-set value + when(mRingerModeLiveData.getValue()).thenReturn(-1); + when(mRingerModeInternalLiveData.getValue()).thenReturn(-1); + mCallback = mock(VolumeDialogControllerImpl.C.class); mStatusBar = mock(StatusBar.class); mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mCallback, mStatusBar, - mBroadcastDispatcher); + mBroadcastDispatcher, mRingerModeTracker); mVolumeController.setEnableDialogs(true, true); } + @After + public void tearDown() { + mVolumeController.destroy(); + } + @Test public void testRegisteredWithDispatcher() { verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class), @@ -109,7 +131,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { TestableVolumeDialogControllerImpl nullStatusBarTestableDialog = new TestableVolumeDialogControllerImpl( - mContext, callback, null, mBroadcastDispatcher); + mContext, callback, null, mBroadcastDispatcher, mRingerModeTracker); nullStatusBarTestableDialog.setEnableDialogs(true, true); nullStatusBarTestableDialog.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI); verify(callback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); @@ -127,12 +149,26 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token); } + @Test + public void testRingerModeLiveDataObserving() { + verify(mRingerModeLiveData).observeForever(any()); + verify(mRingerModeInternalLiveData).observeForever(any()); + } + + @Test + public void testRingerModeOnDestroy_observersRemoved() { + mVolumeController.destroy(); + + verify(mRingerModeLiveData).removeObserver(any()); + verify(mRingerModeInternalLiveData).removeObserver(any()); + } + static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl { TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s, - BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher, RingerModeTracker ringerModeTracker) { super( context, broadcastDispatcher, - s == null ? Optional.empty() : Optional.of(() -> s)); + s == null ? Optional.empty() : Optional.of(() -> s), ringerModeTracker); mCallbacks = callback; } } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 190b443227ce..5b052df75ede 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -25,7 +25,7 @@ java_defaults { ], static_libs: [ "androidx.annotation_annotation", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", "android.hardware.tetheroffload.config-V1.0-java", diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 350980137f3e..cc095a0bb4a7 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -571,9 +571,8 @@ public class TetheringManager { /** * Configure tethering with static IPv4 assignment. * - * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be - * started, but will only be able to offer the client address. The two addresses must - * be in the same prefix. + * A DHCP server will be started, but will only be able to offer the client address. + * The two addresses must be in the same prefix. * * @param localIPv4Address The preferred local IPv4 link address to use. * @param clientAddress The static client address. @@ -584,10 +583,7 @@ public class TetheringManager { @NonNull final LinkAddress clientAddress) { Objects.requireNonNull(localIPv4Address); Objects.requireNonNull(clientAddress); - if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength() - || !localIPv4Address.isIpv4() || !clientAddress.isIpv4() - || !new IpPrefix(localIPv4Address.toString()).equals( - new IpPrefix(clientAddress.toString()))) { + if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) { throw new IllegalArgumentException("Invalid server or client addresses"); } @@ -657,6 +653,19 @@ public class TetheringManager { } /** + * Check whether the two addresses are ipv4 and in the same prefix. + * @hide + */ + public static boolean checkStaticAddressConfiguration( + @NonNull final LinkAddress localIPv4Address, + @NonNull final LinkAddress clientAddress) { + return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength() + && localIPv4Address.isIpv4() && clientAddress.isIpv4() + && new IpPrefix(localIPv4Address.toString()).equals( + new IpPrefix(clientAddress.toString())); + } + + /** * Get a TetheringRequestParcel from the configuration * @hide */ diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java index d6bc063210b3..82a26beadacf 100644 --- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -18,10 +18,12 @@ package android.net.dhcp; import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; -import android.annotation.NonNull; import android.net.LinkAddress; import android.util.ArraySet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.net.Inet4Address; import java.util.Collection; import java.util.Collections; @@ -160,6 +162,17 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { return this; } + /** + * Set the client address to tell DHCP server only offer this address. + * The client's prefix length is the same as server's. + * + * <p>If not set, the default value is null. + */ + public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) { + this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr); + return this; + } + private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) { int[] res = new int[addrs.size()]; int i = 0; diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index c5478d2e1a14..1dac5b784649 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -18,6 +18,7 @@ package android.net.ip; import static android.net.InetAddresses.parseNumericAddress; import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static android.net.util.NetworkConstants.FF; @@ -33,6 +34,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; +import android.net.TetherOffloadRuleParcel; import android.net.TetheredClient; import android.net.TetheringManager; import android.net.TetheringRequestParcel; @@ -40,7 +42,7 @@ import android.net.dhcp.DhcpLeaseParcelable; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.DhcpServingParamsParcelExt; -import android.net.dhcp.IDhcpLeaseCallbacks; +import android.net.dhcp.IDhcpEventCallbacks; import android.net.dhcp.IDhcpServer; import android.net.ip.IpNeighborMonitor.NeighborEvent; import android.net.ip.RouterAdvertisementDaemon.RaParams; @@ -279,6 +281,19 @@ public class IpServer extends StateMachine { return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, dstMac); } + + // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() + // would be error-prone due to generated stable AIDL classes not having a copy constructor. + public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { + final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); + parcel.inputInterfaceIndex = upstreamIfindex; + parcel.outputInterfaceIndex = downstreamIfindex; + parcel.destination = address.getAddress(); + parcel.prefixLength = 128; + parcel.srcL2Address = srcMac.toByteArray(); + parcel.dstL2Address = dstMac.toByteArray(); + return parcel; + } } private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules = new LinkedHashMap<>(); @@ -448,7 +463,7 @@ public class IpServer extends StateMachine { } } - private class DhcpLeaseCallback extends IDhcpLeaseCallbacks.Stub { + private class DhcpLeaseCallback extends IDhcpEventCallbacks.Stub { @Override public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) { final ArrayList<TetheredClient> leases = new ArrayList<>(); @@ -482,6 +497,11 @@ public class IpServer extends StateMachine { } @Override + public void onNewPrefixRequest(IpPrefix currentPrefix) { + //TODO: add specific implementation. + } + + @Override public int getInterfaceVersion() { return this.VERSION; } @@ -492,17 +512,24 @@ public class IpServer extends StateMachine { } } - private boolean startDhcp(Inet4Address addr, int prefixLen) { + private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) { if (mUsingLegacyDhcp) { return true; } + + final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress(); + final int prefixLen = serverLinkAddr.getPrefixLength(); + final Inet4Address clientAddr = clientLinkAddr == null ? null : + (Inet4Address) clientLinkAddr.getAddress(); + final DhcpServingParamsParcel params; params = new DhcpServingParamsParcelExt() .setDefaultRouters(addr) .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS) .setDnsServers(addr) - .setServerAddr(new LinkAddress(addr, prefixLen)) - .setMetered(true); + .setServerAddr(serverLinkAddr) + .setMetered(true) + .setSingleClientAddr(clientAddr); // TODO: also advertise link MTU mDhcpServerStartIndex++; @@ -537,9 +564,10 @@ public class IpServer extends StateMachine { } } - private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) { + private boolean configureDhcp(boolean enable, final LinkAddress serverAddr, + final LinkAddress clientAddr) { if (enable) { - return startDhcp(addr, prefixLen); + return startDhcp(serverAddr, clientAddr); } else { stopDhcp(); return true; @@ -587,7 +615,7 @@ public class IpServer extends StateMachine { // code that calls into NetworkManagementService directly. srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR); mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); - return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); + return configureDhcp(enabled, mIpv4Address, null /* clientAddress */); } mIpv4Address = new LinkAddress(srvAddr, prefixLen); } catch (IllegalArgumentException e) { @@ -624,7 +652,7 @@ public class IpServer extends StateMachine { mLinkProperties.removeRoute(route); } - return configureDhcp(enabled, srvAddr, prefixLen); + return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr); } private String getRandomWifiIPv4Address() { @@ -815,9 +843,7 @@ public class IpServer extends StateMachine { private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) { try { - mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex, - rule.address.getAddress(), mInterfaceParams.macAddr.toByteArray(), - rule.dstMac.toByteArray()); + mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); mIpv6ForwardingRules.put(rule.address, rule); } catch (RemoteException | ServiceSpecificException e) { mLog.e("Could not add IPv6 downstream rule: ", e); @@ -826,7 +852,7 @@ public class IpServer extends StateMachine { private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) { try { - mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress()); + mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); if (removeFromMap) { mIpv6ForwardingRules.remove(rule.address); } @@ -945,7 +971,14 @@ public class IpServer extends StateMachine { } private void maybeConfigureStaticIp(final TetheringRequestParcel request) { - if (request == null) return; + // Ignore static address configuration if they are invalid or null. In theory, static + // addresses should not be invalid here because TetheringManager do not allow caller to + // specify invalid static address configuration. + if (request == null || request.localIPv4Address == null + || request.staticClientAddress == null || !checkStaticAddressConfiguration( + request.localIPv4Address, request.staticClientAddress)) { + return; + } mStaticIpv4ServerAddr = request.localIPv4Address; mStaticIpv4ClientAddr = request.staticClientAddress; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java index bd60594f27bc..639cf65d7936 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java @@ -20,6 +20,7 @@ import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringManager.TETHERING_INVALID; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; @@ -537,6 +538,7 @@ public class EntitlementManager { private static boolean isValidDownstreamType(int type) { switch (type) { case TETHERING_BLUETOOTH: + case TETHERING_ETHERNET: case TETHERING_USB: case TETHERING_WIFI: return true; @@ -650,6 +652,11 @@ public class EntitlementManager { private void handleRequestLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, boolean showEntitlementUi) { + if (!isValidDownstreamType(downstream)) { + receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null); + return; + } + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); if (!isTetherProvisioningRequired(config)) { receiver.send(TETHER_ERROR_NO_ERROR, null); diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp index 1a1c30d1d5f9..620261b375d2 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -39,4 +39,9 @@ android_test { "android.test.base", "android.test.mock", ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], } diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 843a4f19c3b8..b02bb23f9807 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -18,6 +18,7 @@ package android.net; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.net.TetheringManager.TETHERING_ETHERNET; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -57,14 +58,17 @@ import org.junit.runner.RunWith; import java.io.FileDescriptor; import java.net.Inet4Address; +import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; import java.util.Random; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; @RunWith(AndroidJUnit4.class) @MediumTest @@ -109,7 +113,7 @@ public class EthernetTetheringTest { } private void cleanUp() throws Exception { - mTm.stopTethering(TetheringManager.TETHERING_ETHERNET); + mTm.stopTethering(TETHERING_ETHERNET); if (mTetheringEventCallback != null) { mTetheringEventCallback.awaitInterfaceUntethered(); mTetheringEventCallback.unregister(); @@ -150,10 +154,7 @@ public class EthernetTetheringTest { Log.d(TAG, "Including test interfaces"); mEm.setIncludeTestInterfaces(true); - Log.d(TAG, "Requesting tethered interface"); - mTetheredInterfaceRequester.requestInterface(); - - final String iface = mTetheredInterfaceRequester.awaitRequestedInterface(); + final String iface = mTetheredInterfaceRequester.getInterface(); assertEquals("TetheredInterfaceCallback for unexpected interface", mTestIface.getInterfaceName(), iface); @@ -165,14 +166,13 @@ public class EthernetTetheringTest { // This test requires manipulating packets. Skip if there is a physical Ethernet connected. assumeFalse(mEm.isAvailable()); - Log.d(TAG, "Requesting tethered interface"); - mTetheredInterfaceRequester.requestInterface(); + CompletableFuture<String> futureIface = mTetheredInterfaceRequester.requestInterface(); mEm.setIncludeTestInterfaces(true); mTestIface = createTestInterface(); - final String iface = mTetheredInterfaceRequester.awaitRequestedInterface(); + final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals("TetheredInterfaceCallback for unexpected interface", mTestIface.getInterfaceName(), iface); @@ -180,12 +180,54 @@ public class EthernetTetheringTest { } @Test + public void testStaticIpv4() throws Exception { + assumeFalse(mEm.isAvailable()); + + mEm.setIncludeTestInterfaces(true); + + mTestIface = createTestInterface(); + + final String iface = mTetheredInterfaceRequester.getInterface(); + assertEquals("TetheredInterfaceCallback for unexpected interface", + mTestIface.getInterfaceName(), iface); + + assertInvalidStaticIpv4Request(iface, null, null); + assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64"); + assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28"); + assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28"); + assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null); + assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28"); + assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28"); + + final String localAddr = "192.0.2.3/28"; + final String clientAddr = "192.0.2.2/28"; + mTetheringEventCallback = enableEthernetTethering(iface, + requestWithStaticIpv4(localAddr, clientAddr)); + + mTetheringEventCallback.awaitInterfaceTethered(); + assertInterfaceHasIpAddress(iface, localAddr); + + byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray(); + byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray(); + + FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor(); + mTapPacketReader = makePacketReader(fd, getMTU(mTestIface)); + DhcpResults dhcpResults = runDhcp(fd, client1); + assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress); + + try { + runDhcp(fd, client2); + fail("Only one client should get an IP address"); + } catch (TimeoutException expected) { } + + } + + @Test public void testPhysicalEthernet() throws Exception { assumeTrue(mEm.isAvailable()); // Get an interface to use. - mTetheredInterfaceRequester.requestInterface(); - String iface = mTetheredInterfaceRequester.awaitRequestedInterface(); + final String iface = mTetheredInterfaceRequester.getInterface(); // Enable Ethernet tethering and check that it starts. mTetheringEventCallback = enableEthernetTethering(iface); @@ -275,7 +317,8 @@ public class EthernetTetheringTest { } } - private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception { + private MyTetheringEventCallback enableEthernetTethering(String iface, + TetheringRequest request) throws Exception { MyTetheringEventCallback callback = new MyTetheringEventCallback(mTm, iface); mTm.registerTetheringEventCallback(mHandler::post, callback); @@ -286,34 +329,37 @@ public class EthernetTetheringTest { } }; Log.d(TAG, "Starting Ethernet tethering"); - mTm.startTethering( - new TetheringRequest.Builder(TetheringManager.TETHERING_ETHERNET).build(), - mHandler::post /* executor */, startTetheringCallback); + mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback); callback.awaitInterfaceTethered(); return callback; } + private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception { + return enableEthernetTethering(iface, + new TetheringRequest.Builder(TETHERING_ETHERNET).build()); + } + private int getMTU(TestNetworkInterface iface) throws SocketException { NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName()); assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif); return nif.getMTU(); } - private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception { - FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor(); - mTapPacketReader = new TapPacketReader(mHandler, fd, mtu); - mHandler.post(() -> mTapPacketReader.start()); + private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) { + final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu); + mHandler.post(() -> reader.start()); HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS); + return reader; + } + private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception { + FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor(); + mTapPacketReader = makePacketReader(fd, mtu); mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName()); checkTetheredClientCallbacks(fd); } - private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception { - // Create a fake client. - byte[] clientMacAddr = new byte[6]; - new Random().nextBytes(clientMacAddr); - + private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception { // We have to retransmit DHCP requests because IpServer declares itself to be ready before // its DhcpServer is actually started. TODO: fix this race and remove this loop. DhcpPacket offerPacket = null; @@ -323,13 +369,25 @@ public class EthernetTetheringTest { offerPacket = getNextDhcpPacket(); if (offerPacket instanceof DhcpOfferPacket) break; } - assertTrue("No DHCPOFFER received on interface within timeout", - offerPacket instanceof DhcpOfferPacket); + if (!(offerPacket instanceof DhcpOfferPacket)) { + throw new TimeoutException("No DHCPOFFER received on interface within timeout"); + } sendDhcpRequest(fd, offerPacket, clientMacAddr); DhcpPacket ackPacket = getNextDhcpPacket(); - assertTrue("No DHCPACK received on interface within timeout", - ackPacket instanceof DhcpAckPacket); + if (!(ackPacket instanceof DhcpAckPacket)) { + throw new TimeoutException("No DHCPACK received on interface within timeout"); + } + + return ackPacket.toDhcpResults(); + } + + private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception { + // Create a fake client. + byte[] clientMacAddr = new byte[6]; + new Random().nextBytes(clientMacAddr); + + DhcpResults dhcpResults = runDhcp(fd, clientMacAddr); final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected(); assertEquals(1, clients.size()); @@ -337,7 +395,7 @@ public class EthernetTetheringTest { // Check the MAC address. assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress()); - assertEquals(TetheringManager.TETHERING_ETHERNET, client.getTetheringType()); + assertEquals(TETHERING_ETHERNET, client.getTetheringType()); // Check the hostname. assertEquals(1, client.getAddresses().size()); @@ -345,7 +403,6 @@ public class EthernetTetheringTest { assertEquals(DHCP_HOSTNAME, info.getHostname()); // Check the address is the one that was handed out in the DHCP ACK. - DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress()); // Check that the lifetime is correct +/- 10s. @@ -373,8 +430,8 @@ public class EthernetTetheringTest { private final Handler mHandler; private final EthernetManager mEm; - private volatile TetheredInterfaceRequest mRequest; - private volatile String mIface; + private TetheredInterfaceRequest mRequest; + private final CompletableFuture<String> mFuture = new CompletableFuture<>(); TetheredInterfaceRequester(Handler handler, EthernetManager em) { mHandler = handler; @@ -384,25 +441,28 @@ public class EthernetTetheringTest { @Override public void onAvailable(String iface) { Log.d(TAG, "Ethernet interface available: " + iface); - mIface = iface; - mInterfaceAvailableLatch.countDown(); + mFuture.complete(iface); } + @Override - public void onUnavailable() {} + public void onUnavailable() { + mFuture.completeExceptionally(new IllegalStateException("onUnavailable received")); + } - public void requestInterface() { + public CompletableFuture<String> requestInterface() { assertNull("BUG: more than one tethered interface request", mRequest); + Log.d(TAG, "Requesting tethered interface"); mRequest = mEm.requestTetheredInterface(mHandler::post, this); + return mFuture; } - public String awaitRequestedInterface() throws InterruptedException { - assertTrue("No tethered interface available after " + TIMEOUT_MS + "ms", - mInterfaceAvailableLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - return mIface; + public String getInterface() throws Exception { + return requestInterface().get(TIMEOUT_MS, TimeUnit.MILLISECONDS); } public void release() { if (mRequest != null) { + mFuture.obtrudeException(new IllegalStateException("Request already released")); mRequest.release(); mRequest = null; } @@ -442,6 +502,34 @@ public class EthernetTetheringTest { assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope()); } + private TetheringRequest requestWithStaticIpv4(String local, String client) { + LinkAddress localAddr = local == null ? null : new LinkAddress(local); + LinkAddress clientAddr = client == null ? null : new LinkAddress(client); + return new TetheringRequest.Builder(TETHERING_ETHERNET) + .setStaticIpv4Addresses(localAddr, clientAddr).build(); + } + + private void assertInvalidStaticIpv4Request(String iface, String local, String client) + throws Exception { + try { + enableEthernetTethering(iface, requestWithStaticIpv4(local, client)); + fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client); + } catch (IllegalArgumentException | NullPointerException expected) { } + } + + private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception { + LinkAddress expectedAddr = new LinkAddress(expected); + NetworkInterface nif = NetworkInterface.getByName(iface); + for (InterfaceAddress ia : nif.getInterfaceAddresses()) { + final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength()); + if (expectedAddr.equals(addr)) { + return; + } + } + fail("Expected " + iface + " to have IP address " + expected + ", found " + + nif.getInterfaceAddresses()); + } + private TestNetworkInterface createTestInterface() throws Exception { TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class); TestNetworkInterface iface = tnm.createTapInterface(); diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml index 530bc0788a78..4ff1d3777f25 100644 --- a/packages/Tethering/tests/unit/AndroidManifest.xml +++ b/packages/Tethering/tests/unit/AndroidManifest.xml @@ -20,7 +20,16 @@ <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> + <service + android:name="com.android.server.connectivity.tethering.MockTetheringService" + android:permission="android.permission.TETHER_PRIVILEGED" + android:exported="true"> + <intent-filter> + <action android:name="com.android.server.connectivity.tethering.TetheringService"/> + </intent-filter> + </service> </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.networkstack.tethering.tests.unit" android:label="Tethering service tests"> diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java index e8add9830b5f..f8eb1476bad0 100644 --- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java +++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java @@ -42,7 +42,9 @@ import java.util.stream.IntStream; @SmallTest public class DhcpServingParamsParcelExtTest { private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123"); + private static final Inet4Address TEST_CLIENT_ADDRESS = inet4Addr("192.168.0.42"); private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b; + private static final int TEST_CLIENT_ADDRESS_PARCELED = 0xc0a8002a; private static final int TEST_PREFIX_LENGTH = 17; private static final int TEST_LEASE_TIME_SECS = 120; private static final int TEST_MTU = 1000; @@ -105,6 +107,12 @@ public class DhcpServingParamsParcelExtTest { assertFalse(mParcel.metered); } + @Test + public void testSetClientAddr() { + mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS); + assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr); + } + private static Inet4Address inet4Addr(String addr) { return (Inet4Address) parseNumericAddress(addr); } diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 3106e0e5e1c6..fdfdae837d51 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -43,7 +43,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; @@ -66,6 +65,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; +import android.net.TetherOffloadRuleParcel; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServer; import android.net.dhcp.IDhcpServerCallbacks; @@ -85,6 +85,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; @@ -92,6 +93,7 @@ import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.net.InetAddress; +import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest @@ -514,6 +516,65 @@ public class IpServerTest { mLooper.dispatchAll(); } + /** + * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable + * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as: + * + * private void checkFooCalled(StableParcelable p, ...) { + * ArgumentCaptor<FooParam> captor = ArgumentCaptor.forClass(FooParam.class); + * verify(mMock).foo(captor.capture()); + * Foo foo = captor.getValue(); + * assertFooMatchesExpectations(foo); + * } + * + * almost works, but not quite. This is because if the code under test calls foo() twice, the + * first call to checkFooCalled() matches both the calls, putting both calls into the captor, + * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito + * features such as never(), inOrder(), etc. + * + * This approach isn't great because if the match fails, the error message is unhelpful + * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does + * work. + * + * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the + * TooManyActualInvocations problem described above by forcing the caller of the custom assert + * method to specify all expected invocations in one call. This is useful when the stable + * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and + * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here + * because there is no such object. + */ + private static class TetherOffloadRuleParcelMatcher implements + ArgumentMatcher<TetherOffloadRuleParcel> { + public final int upstreamIfindex; + public final InetAddress dst; + public final MacAddress dstMac; + + TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) { + this.upstreamIfindex = upstreamIfindex; + this.dst = dst; + this.dstMac = dstMac; + } + + public boolean matches(TetherOffloadRuleParcel parcel) { + return upstreamIfindex == parcel.inputInterfaceIndex + && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex) + && Arrays.equals(dst.getAddress(), parcel.destination) + && (128 == parcel.prefixLength) + && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address) + && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); + } + + public String toString() { + return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s", + upstreamIfindex, dst.getHostAddress(), dstMac); + } + } + + private TetherOffloadRuleParcel matches( + int upstreamIfindex, InetAddress dst, MacAddress dstMac) { + return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); + } + @Test public void addRemoveipv6ForwardingRules() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */); @@ -537,13 +598,11 @@ public class IpServerTest { // Events on this interface are received and sent to netd. recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX), - eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray())); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); reset(mNetd); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX), - eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray())); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); reset(mNetd); // Link-local and multicast neighbors are ignored. @@ -554,12 +613,12 @@ public class IpServerTest { // A neighbor that is no longer valid causes the rule to be removed. recvNewNeigh(myIfindex, neighA, NUD_FAILED, macA); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress())); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); reset(mNetd); // A neighbor that is deleted causes the rule to be removed. recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress())); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); reset(mNetd); // Upstream changes result in deleting and re-adding the rules. @@ -571,22 +630,16 @@ public class IpServerTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName(UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp); - inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2), - eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray())); - inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), - eq(neighA.getAddress())); - inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2), - eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray())); - inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), - eq(neighB.getAddress())); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA)); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB)); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); reset(mNetd); // When the upstream is lost, rules are removed. dispatchTetherConnectionChanged(null, null); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2), - eq(neighA.getAddress())); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2), - eq(neighB.getAddress())); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA)); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB)); reset(mNetd); // If the upstream is IPv4-only, no rules are added. @@ -599,31 +652,27 @@ public class IpServerTest { lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX), - eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray())); - verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(), - eq(neighA.getAddress()), any(), any()); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); + verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); // If upstream IPv6 connectivity is lost, rules are removed. reset(mNetd); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress())); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); // When the interface goes down, rules are removed. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp); recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX), - eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray())); - verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX), - eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray())); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); reset(mNetd); mIpServer.stop(); mLooper.dispatchAll(); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress())); - verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress())); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); reset(mNetd); } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 0a7850b68014..6695eed0ff65 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -17,8 +17,10 @@ package com.android.server.connectivity.tethering; import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; @@ -31,7 +33,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; @@ -70,8 +71,6 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest @@ -253,19 +252,16 @@ public final class EntitlementManagerTest { @Test public void testRequestLastEntitlementCacheValue() throws Exception { - final CountDownLatch mCallbacklatch = new CountDownLatch(1); // 1. Entitlement check is not required. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; ResultReceiver receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(0, mEnMgr.uiProvisionCount); mEnMgr.reset(); @@ -275,12 +271,10 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(0, mEnMgr.uiProvisionCount); mEnMgr.reset(); // 3. No cache value and ui entitlement check is needed. @@ -289,12 +283,10 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(1, mEnMgr.uiProvisionCount); mEnMgr.reset(); // 4. Cache value is TETHER_ERROR_PROVISIONING_FAILED and don't need to run entitlement @@ -304,12 +296,10 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(0, mEnMgr.uiProvisionCount); mEnMgr.reset(); // 5. Cache value is TETHER_ERROR_PROVISIONING_FAILED and ui entitlement check is needed. @@ -318,12 +308,10 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(1, mEnMgr.uiProvisionCount); mEnMgr.reset(); // 6. Cache value is TETHER_ERROR_NO_ERROR. @@ -332,12 +320,10 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(0, mEnMgr.uiProvisionCount); mEnMgr.reset(); // 7. Test get value for other downstream type. @@ -345,20 +331,24 @@ public final class EntitlementManagerTest { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode); - mCallbacklatch.countDown(); } }; mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); assertEquals(0, mEnMgr.uiProvisionCount); mEnMgr.reset(); - } - - void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { - if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to receive callback"); - } + // 8. Test get value for invalid downstream type. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode); + } + }; + mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); } @Test @@ -471,6 +461,22 @@ public final class EntitlementManagerTest { mLooper.dispatchAll(); assertEquals(0, mEnMgr.uiProvisionCount); assertEquals(3, mEnMgr.silentProvisionCount); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 7. start ui provisioning, upstream is mobile, downstream is ethernet + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_ETHERNET, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 8. downstream is invalid + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI_P2P, true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); mEnMgr.reset(); } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java new file mode 100644 index 000000000000..912124357ca8 --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity.tethering; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.ip.IpServer.STATE_LOCAL_ONLY; +import static android.net.ip.IpServer.STATE_TETHERED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.RouteInfo; +import android.net.ip.IpServer; +import android.net.util.SharedLog; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IPv6TetheringCoordinatorTest { + private static final String TEST_DNS_SERVER = "2001:4860:4860::8888"; + private static final String TEST_INTERFACE = "test_rmnet0"; + private static final String TEST_IPV6_ADDRESS = "2001:db8::1/64"; + private static final String TEST_IPV4_ADDRESS = "192.168.100.1/24"; + + private IPv6TetheringCoordinator mIPv6TetheringCoordinator; + private ArrayList<IpServer> mNotifyList; + + @Mock private SharedLog mSharedLog; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); + mNotifyList = new ArrayList<IpServer>(); + mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mSharedLog); + } + + private UpstreamNetworkState createDualStackUpstream(final int transportType) { + final Network network = mock(Network.class); + final NetworkCapabilities netCap = + new NetworkCapabilities.Builder().addTransportType(transportType).build(); + final InetAddress dns = InetAddresses.parseNumericAddress(TEST_DNS_SERVER); + final LinkProperties linkProp = new LinkProperties(); + linkProp.setInterfaceName(TEST_INTERFACE); + linkProp.addLinkAddress(new LinkAddress(TEST_IPV6_ADDRESS)); + linkProp.addLinkAddress(new LinkAddress(TEST_IPV4_ADDRESS)); + linkProp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, TEST_INTERFACE, RTN_UNICAST)); + linkProp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, TEST_INTERFACE, + RTN_UNICAST)); + linkProp.addDnsServer(dns); + return new UpstreamNetworkState(linkProp, netCap, network); + } + + private void assertOnlyOneV6AddressAndNoV4(LinkProperties lp) { + assertEquals(lp.getInterfaceName(), TEST_INTERFACE); + assertFalse(lp.hasIpv4Address()); + final List<LinkAddress> addresses = lp.getLinkAddresses(); + assertEquals(addresses.size(), 1); + final LinkAddress v6Address = addresses.get(0); + assertEquals(v6Address, new LinkAddress(TEST_IPV6_ADDRESS)); + } + + @Test + public void testUpdateIpv6Upstream() throws Exception { + // 1. Add first IpServer. + final IpServer firstServer = mock(IpServer.class); + mNotifyList.add(firstServer); + mIPv6TetheringCoordinator.addActiveDownstream(firstServer, STATE_TETHERED); + verify(firstServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null); + verifyNoMoreInteractions(firstServer); + + // 2. Add second IpServer and it would not have ipv6 tethering. + final IpServer secondServer = mock(IpServer.class); + mNotifyList.add(secondServer); + mIPv6TetheringCoordinator.addActiveDownstream(secondServer, STATE_LOCAL_ONLY); + verifyNoMoreInteractions(secondServer); + reset(firstServer, secondServer); + + // 3. No upstream. + mIPv6TetheringCoordinator.updateUpstreamNetworkState(null); + verify(secondServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null); + reset(firstServer, secondServer); + + // 4. Update ipv6 mobile upstream. + final UpstreamNetworkState mobileUpstream = createDualStackUpstream(TRANSPORT_CELLULAR); + final ArgumentCaptor<LinkProperties> lp = ArgumentCaptor.forClass(LinkProperties.class); + mIPv6TetheringCoordinator.updateUpstreamNetworkState(mobileUpstream); + verify(firstServer).sendMessage(eq(IpServer.CMD_IPV6_TETHER_UPDATE), eq(0), eq(0), + lp.capture()); + final LinkProperties v6OnlyLink = lp.getValue(); + assertOnlyOneV6AddressAndNoV4(v6OnlyLink); + verifyNoMoreInteractions(firstServer); + verifyNoMoreInteractions(secondServer); + reset(firstServer, secondServer); + + // 5. Remove first IpServer. + mNotifyList.remove(firstServer); + mIPv6TetheringCoordinator.removeActiveDownstream(firstServer); + verify(firstServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null); + verify(secondServer).sendMessage(eq(IpServer.CMD_IPV6_TETHER_UPDATE), eq(0), eq(0), + lp.capture()); + final LinkProperties localOnlyLink = lp.getValue(); + assertNotNull(localOnlyLink); + assertNotEquals(localOnlyLink, v6OnlyLink); + reset(firstServer, secondServer); + + // 6. Remove second IpServer. + mNotifyList.remove(secondServer); + mIPv6TetheringCoordinator.removeActiveDownstream(secondServer); + verifyNoMoreInteractions(firstServer); + verify(secondServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java new file mode 100644 index 000000000000..355ece9a44a1 --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.connectivity.tethering; + +import static org.mockito.Mockito.mock; + +import android.content.Intent; +import android.net.ITetheringConnector; +import android.os.Binder; +import android.os.IBinder; + +public class MockTetheringService extends TetheringService { + private final Tethering mTethering = mock(Tethering.class); + + @Override + public IBinder onBind(Intent intent) { + return new MockTetheringConnector(super.onBind(intent)); + } + + @Override + public Tethering makeTethering(TetheringDependencies deps) { + return mTethering; + } + + public Tethering getTethering() { + return mTethering; + } + + public class MockTetheringConnector extends Binder { + final IBinder mBase; + MockTetheringConnector(IBinder base) { + mBase = base; + } + + public ITetheringConnector getTetheringConnector() { + return ITetheringConnector.Stub.asInterface(mBase); + } + + public MockTetheringService getService() { + return MockTetheringService.this; + } + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java new file mode 100644 index 000000000000..d9d3e73eb4e3 --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity.tethering; + +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.net.IIntResultListener; +import android.net.ITetheringConnector; +import android.net.ITetheringEventCallback; +import android.net.TetheringRequestParcel; +import android.os.ResultReceiver; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.rule.ServiceTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class TetheringServiceTest { + private static final String TEST_IFACE_NAME = "test_wlan0"; + private static final String TEST_CALLER_PKG = "test_pkg"; + @Mock private ITetheringEventCallback mITetheringEventCallback; + @Rule public ServiceTestRule mServiceTestRule; + private Tethering mTethering; + private Intent mMockServiceIntent; + private ITetheringConnector mTetheringConnector; + + private class TestTetheringResult extends IIntResultListener.Stub { + private int mResult = -1; // Default value that does not match any result code. + @Override + public void onResult(final int resultCode) { + mResult = resultCode; + } + + public void assertResult(final int expected) { + assertEquals(expected, mResult); + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mServiceTestRule = new ServiceTestRule(); + mMockServiceIntent = new Intent( + InstrumentationRegistry.getTargetContext(), + MockTetheringService.class); + final MockTetheringConnector mockConnector = + (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent); + mTetheringConnector = mockConnector.getTetheringConnector(); + final MockTetheringService service = mockConnector.getService(); + mTethering = service.getTethering(); + verify(mTethering).startStateMachineUpdaters(); + when(mTethering.hasTetherableConfiguration()).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + mServiceTestRule.unbindService(); + } + + @Test + public void testTether() throws Exception { + when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).tether(TEST_IFACE_NAME); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testUntether() throws Exception { + when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).untether(TEST_IFACE_NAME); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testSetUsbTethering() throws Exception { + when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).setUsbTethering(true /* enable */); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testStartTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).startTethering(eq(request), eq(result)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testStopTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).stopTethering(TETHERING_WIFI); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testRequestLatestTetheringEntitlementResult() throws Exception { + final ResultReceiver result = new ResultReceiver(null); + mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, + true /* showEntitlementUi */, TEST_CALLER_PKG); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI), + eq(result), eq(true) /* showEntitlementUi */); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testRegisterTetheringEventCallback() throws Exception { + mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testUnregisterTetheringEventCallback() throws Exception { + mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mTethering).unregisterTetheringEventCallback( + eq(mITetheringEventCallback)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testStopAllTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).untetherAll(); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testIsTetheringSupported() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index a59c6fd9e193..2955903c84f5 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -38,6 +38,7 @@ import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; @@ -140,7 +141,9 @@ import com.android.networkstack.tethering.R; import com.android.testutils.MiscAssertsKt; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -439,6 +442,18 @@ public class TetheringTest { return buildMobileUpstreamState(false, true, true); } + // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and + // after use. + @BeforeClass + public static void setupOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @AfterClass + public static void tearDownOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -1654,10 +1669,13 @@ public class TetheringTest { } @Test - public void testRequestStaticServerIp() throws Exception { - final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24"); - final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24"); - final String serverAddr = "192.168.20.1"; + public void testRequestStaticIp() throws Exception { + final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24"); + final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24"); + final String serverAddr = "192.168.0.123"; + final int clientAddrParceled = 0xc0a8002a; + final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor = + ArgumentCaptor.forClass(DhcpServingParamsParcel.class); mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, serverLinkAddr, clientLinkAddr), null); mLooper.dispatchAll(); @@ -1666,8 +1684,12 @@ public class TetheringTest { sendUsbBroadcast(true, true, true, TETHERING_USB); mLooper.dispatchAll(); verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr))); - - // TODO: test static client address. + verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(), + any()); + final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue(); + assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress()); + assertEquals(24, params.serverAddrPrefixLength); + assertEquals(clientAddrParceled, params.clientAddr); } // TODO: Test that a request for hotspot mode doesn't interfere with an diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 93bffe9f54e4..52a82dd2a156 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -36,10 +36,12 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.PackageMonitor; import libcore.io.IoUtils; @@ -261,22 +263,7 @@ public class WallpaperBackupAgent extends BackupAgent { // And reset to the wallpaper service we should be using ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); - if (servicePackageExists(wpService)) { - Slog.i(TAG, "Using wallpaper service " + wpService); - mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM); - if (!lockImageStage.exists()) { - // We have a live wallpaper and no static lock image, - // allow live wallpaper to show "through" on lock screen. - mWm.clear(FLAG_LOCK); - } - } else { - // If we've restored a live wallpaper, but the component doesn't exist, - // we should log it as an error so we can easily identify the problem - // in reports from users - if (wpService != null) { - Slog.e(TAG, "Wallpaper service " + wpService + " isn't available."); - } - } + updateWallpaperComponent(wpService, !lockImageStage.exists()); } catch (Exception e) { Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage()); } finally { @@ -293,6 +280,28 @@ public class WallpaperBackupAgent extends BackupAgent { } } + @VisibleForTesting + void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException { + if (servicePackageExists(wpService)) { + Slog.i(TAG, "Using wallpaper service " + wpService); + mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM); + if (applyToLock) { + // We have a live wallpaper and no static lock image, + // allow live wallpaper to show "through" on lock screen. + mWm.clear(FLAG_LOCK); + } + } else { + // If we've restored a live wallpaper, but the component doesn't exist, + // we should log it as an error so we can easily identify the problem + // in reports from users + if (wpService != null) { + applyComponentAtInstall(wpService, applyToLock); + Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. " + + " Will try to apply later"); + } + } + } + private void restoreFromStage(File stage, File info, String hintTag, int which) throws IOException { if (stage.exists()) { @@ -372,7 +381,8 @@ public class WallpaperBackupAgent extends BackupAgent { return (value == null) ? defValue : Integer.parseInt(value); } - private boolean servicePackageExists(ComponentName comp) { + @VisibleForTesting + boolean servicePackageExists(ComponentName comp) { try { if (comp != null) { final IPackageManager pm = AppGlobals.getPackageManager(); @@ -401,4 +411,53 @@ public class WallpaperBackupAgent extends BackupAgent { throws IOException { // Intentionally blank } + + private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) { + PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock); + packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true); + } + + @VisibleForTesting + PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) { + return new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + if (!isDeviceInRestore()) { + // We don't want to reapply the wallpaper outside a restore. + unregister(); + return; + } + + if (componentName.getPackageName().equals(packageName)) { + Slog.d(TAG, "Applying component " + componentName); + mWm.setWallpaperComponent(componentName); + if (applyToLock) { + try { + mWm.clear(FLAG_LOCK); + } catch (IOException e) { + Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e); + } + } + // We're only expecting to restore the wallpaper component once. + unregister(); + } + } + }; + } + + @VisibleForTesting + boolean isDeviceInRestore() { + try { + boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE) == 0; + boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext() + .getContentResolver(), + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) == + Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED; + return isInSetup || isInDeferredSetup; + } catch (Settings.SettingNotFoundException e) { + Slog.w(TAG, "Failed to check if the user is in restore: " + e); + return false; + } + } }
\ No newline at end of file diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 255fdef36cd7..4367075abc74 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wallpaperbackup.tests; +package com.android.wallpaperbackup; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; @@ -26,17 +26,22 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.WallpaperManager; import android.app.backup.FullBackupDataOutput; +import android.content.ComponentName; +import android.content.Context; import android.content.SharedPreferences; import android.os.UserHandle; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.content.PackageMonitor; import com.android.wallpaperbackup.WallpaperBackupAgent; import com.android.wallpaperbackup.utils.ContextWithServiceOverrides; @@ -58,6 +63,7 @@ import java.util.List; public class WallpaperBackupAgentTest { private static final String SYSTEM_GENERATION = "system_gen"; private static final String LOCK_GENERATION = "lock_gen"; + private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package"; private static final int TEST_SYSTEM_WALLPAPER_ID = 1; private static final int TEST_LOCK_WALLPAPER_ID = 2; @@ -66,11 +72,13 @@ public class WallpaperBackupAgentTest { @Mock private WallpaperManager mWallpaperManager; @Mock private SharedPreferences mSharedPreferences; @Mock private SharedPreferences.Editor mSharedPreferenceEditor; + @Mock private Context mMockContext; @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private ContextWithServiceOverrides mContext; private IsolatedWallpaperBackupAgent mWallpaperBackupAgent; + private ComponentName mWallpaperComponent; @Before public void setUp() { @@ -88,6 +96,8 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot()); mWallpaperBackupAgent.attach(mContext); mWallpaperBackupAgent.onCreate(); + + mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, ""); } @Test @@ -130,6 +140,69 @@ public class WallpaperBackupAgentTest { inOrder.verify(mSharedPreferenceEditor).apply(); } + @Test + public void updateWallpaperComponent_doesApplyLater() throws IOException { + mWallpaperBackupAgent.mIsDeviceInRestore = true; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* applyToLock */ true); + + // Imitate wallpaper component installation. + mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, + /* uid */0); + + verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent); + verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK)); + } + + @Test + public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen() + throws IOException { + mWallpaperBackupAgent.mIsDeviceInRestore = true; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* applyToLock */ false); + + // Imitate wallpaper component installation. + mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, + /* uid */0); + + verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent); + verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK)); + } + + @Test + public void updateWallpaperComponent_deviceNotInRestore_doesNotApply() + throws IOException { + mWallpaperBackupAgent.mIsDeviceInRestore = false; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* applyToLock */ true); + + // Imitate wallpaper component installation. + mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, + /* uid */0); + + verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent); + verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK)); + } + + @Test + public void updateWallpaperComponent_differentPackageInstalled_doesNotApply() + throws IOException { + mWallpaperBackupAgent.mIsDeviceInRestore = false; + + mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, + /* applyToLock */ true); + + // Imitate "wrong" wallpaper component installation. + mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"", + /* uid */0); + + verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent); + verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK)); + } + private void mockUnbackedUpState() { mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1); @@ -162,6 +235,8 @@ public class WallpaperBackupAgentTest { private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent { File mWallpaperBaseDirectory; List<File> mBackedUpFiles = new ArrayList<>(); + PackageMonitor mWallpaperPackageMonitor; + boolean mIsDeviceInRestore = false; IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) { mWallpaperBaseDirectory = wallpaperBaseDirectory; @@ -181,5 +256,27 @@ public class WallpaperBackupAgentTest { public SharedPreferences getSharedPreferences(File file, int mode) { return mSharedPreferences; } + + @Override + boolean servicePackageExists(ComponentName comp) { + return false; + } + + @Override + boolean isDeviceInRestore() { + return mIsDeviceInRestore; + } + + @Override + PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, + boolean applyToLock) { + mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock); + return mWallpaperPackageMonitor; + } + + @Override + public Context getBaseContext() { + return mMockContext; + } } } diff --git a/proto/src/typed_features.proto b/proto/src/typed_features.proto new file mode 100644 index 000000000000..c2b3b18ea7c8 --- /dev/null +++ b/proto/src/typed_features.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.service; +option java_multiple_files = true; + +// This message is to specify feature params that are a list of strings. +message StringListParamProto { + repeated string element = 1; +}
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index c8b6d8dfd1b7..132b6927badd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -43,12 +43,10 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.graphics.GraphicBuffer; import android.graphics.ParcelableColorSpace; -import android.graphics.Point; -import android.graphics.Rect; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.DisplayManagerInternal; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -66,7 +64,6 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; -import android.view.SurfaceControl; import android.view.SurfaceControl.ScreenshotGraphicBuffer; import android.view.View; import android.view.WindowInfo; @@ -1010,53 +1007,29 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return; } - final Display display = DisplayManagerGlobal.getInstance() - .getRealDisplay(displayId); - if (display == null) { - sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, - callback); + // Private virtual displays are created by the ap and is not allowed to access by other + // aps. We assume the contents on this display should not be captured. + final DisplayManager displayManager = + (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); + final Display display = displayManager.getDisplay(displayId); + if ((display == null) || (display.getType() == Display.TYPE_VIRTUAL + && (display.getFlags() & Display.FLAG_PRIVATE) != 0)) { + sendScreenshotFailure( + AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback); return; } - sendScreenshotSuccess(display, callback); - } - - private ScreenshotGraphicBuffer takeScreenshotBuffer(Display display) { - final Point displaySize = new Point(); - // TODO (b/145893483): calling new API with the display as a parameter - // when surface control supported. - final IBinder token = SurfaceControl.getInternalDisplayToken(); - final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - final int rotation = display.getRotation(); - display.getRealSize(displaySize); - - return SurfaceControl.screenshotToBuffer(token, crop, displaySize.x, displaySize.y, - false, rotation); - } - - private void sendScreenshotSuccess(Display display, RemoteCallback callback) { final long identity = Binder.clearCallingIdentity(); try { mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final ScreenshotGraphicBuffer screenshotBuffer = takeScreenshotBuffer(display); - final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); - try (HardwareBuffer hardwareBuffer = - HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) { - final ParcelableColorSpace colorSpace = - new ParcelableColorSpace(screenshotBuffer.getColorSpace()); - - final Bundle payload = new Bundle(); - payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, - AccessibilityService.TAKE_SCREENSHOT_SUCCESS); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, - hardwareBuffer); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); - payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, - SystemClock.uptimeMillis()); - - // Send back the result. - callback.sendResult(payload); - hardwareBuffer.close(); + final ScreenshotGraphicBuffer screenshotBuffer = LocalServices + .getService(DisplayManagerInternal.class) + .screenshotWithoutSecureLayer(displayId); + if (screenshotBuffer != null) { + sendScreenshotSuccess(screenshotBuffer, callback); + } else { + sendScreenshotFailure( + AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback); } }, null).recycleOnUse()); } finally { @@ -1064,6 +1037,29 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + private void sendScreenshotSuccess(ScreenshotGraphicBuffer screenshotBuffer, + RemoteCallback callback) { + final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); + try (HardwareBuffer hardwareBuffer = + HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) { + final ParcelableColorSpace colorSpace = + new ParcelableColorSpace(screenshotBuffer.getColorSpace()); + + final Bundle payload = new Bundle(); + payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, + AccessibilityService.TAKE_SCREENSHOT_SUCCESS); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, + hardwareBuffer); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); + payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, + SystemClock.uptimeMillis()); + + // Send back the result. + callback.sendResult(payload); + hardwareBuffer.close(); + } + } + private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode, RemoteCallback callback) { mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1b180e3357d7..7230b00f87ad 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -197,7 +197,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final MainHandler mMainHandler; - private final SystemActionPerformer mSystemActionPerformer; + // Lazily initialized - access through getSystemActionPerfomer() + private SystemActionPerformer mSystemActionPerformer; private MagnificationController mMagnificationController; @@ -295,8 +296,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); - mSystemActionPerformer = - new SystemActionPerformer(mContext, mWindowManagerService, null, this); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); @@ -414,7 +413,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && component.getPackageName().equals(packageName)) || userState.mCrashedServices.removeIf(component -> component != null && component.getPackageName().equals(packageName)); - if (reboundAService) { + // Reloads the installed services info to make sure the rebound service could + // get a new one. + userState.mInstalledServices.clear(); + final boolean configurationChanged = + readConfigurationForUserStateLocked(userState); + if (reboundAService || configurationChanged) { onUserStateChangedLocked(userState); } migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName); @@ -667,7 +671,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( Manifest.permission.MANAGE_ACCESSIBILITY, FUNCTION_REGISTER_SYSTEM_ACTION); - mSystemActionPerformer.registerSystemAction(actionId, action); + getSystemActionPerformer().registerSystemAction(actionId, action); } /** @@ -680,7 +684,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( Manifest.permission.MANAGE_ACCESSIBILITY, FUNCTION_UNREGISTER_SYSTEM_ACTION); - mSystemActionPerformer.unregisterSystemAction(actionId); + getSystemActionPerformer().unregisterSystemAction(actionId); + } + + private SystemActionPerformer getSystemActionPerformer() { + if (mSystemActionPerformer == null) { + mSystemActionPerformer = + new SystemActionPerformer(mContext, mWindowManagerService, null, this); + } + return mSystemActionPerformer; } @Override @@ -792,7 +804,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, - mSecurityPolicy, this, mWindowManagerService, mSystemActionPerformer, + mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, flags); onUserStateChangedLocked(getCurrentUserStateLocked()); } @@ -1503,7 +1515,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (service == null) { service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - this, mWindowManagerService, mSystemActionPerformer, + this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService); } else if (userState.mBoundServices.contains(service)) { continue; @@ -2741,7 +2753,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState, mContext, COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, AccessibilityManagerService.this, mWindowManagerService, - mSystemActionPerformer, mA11yWindowManager, mActivityTaskManagerService) { + getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) { @Override public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { return true; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java index ff59c24a7ca2..20a11bd9acd3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import android.annotation.NonNull; +import android.app.ActivityManager; import android.os.ShellCommand; import android.os.UserHandle; @@ -83,7 +84,7 @@ final class AccessibilityShellCommand extends ShellCommand { return null; } } - return UserHandle.USER_SYSTEM; + return ActivityManager.getCurrentUser(); } @Override diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 8c1360cca940..ca1b27bd261e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -3669,6 +3669,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Default launcher from package manager. final ComponentName defaultLauncher = mPackageManagerInternal .getDefaultHomeActivity(UserHandle.getUserId(callingUid)); + if (defaultLauncher == null) { + return; + } int defaultLauncherUid = 0; try { defaultLauncherUid = mPackageManager.getApplicationInfo( diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java new file mode 100644 index 000000000000..3612e093c8bd --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.os.Bundle; +import android.os.Handler; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.Collections; +import java.util.Optional; +import java.util.function.Consumer; + + +/** + * Controls the interaction with the IME for the inline suggestion sessions. + */ +final class AutofillInlineSessionController { + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + @NonNull + private final Handler mHandler; + + @GuardedBy("mLock") + private AutofillInlineSuggestionsRequestSession mSession; + + AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal, + int userId, ComponentName componentName, Handler handler, Object lock) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mHandler = handler; + mLock = lock; + } + + + /** + * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}. + * + * @param autofillId the Id of the field for which the request is for. + * @param requestConsumer the callback which will be invoked when IME responded or if it times + * out waiting for IME response. + */ + @GuardedBy("mLock") + void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId, + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { + // TODO(b/151123764): rename the method to better reflect what it does. + if (mSession != null) { + // Send an empty response to IME and destroy the existing session. + mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(), + new InlineSuggestionsResponse(Collections.EMPTY_LIST)); + mSession.destroySessionLocked(); + } + // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the + // same field. + mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId, + mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras); + mSession.onCreateInlineSuggestionsRequestLocked(); + + } + + /** + * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request. + * + * <p> The caller is responsible for making sure Autofill hears back from IME before calling + * this method, using the {@code requestConsumer} provided when calling {@link + * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}. + */ + @GuardedBy("mLock") + Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() { + if (mSession != null) { + return mSession.getInlineSuggestionsRequestLocked(); + } + return Optional.empty(); + } + + /** + * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent + * to the IME. + */ + @GuardedBy("mLock") + boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) { + if (mSession != null) { + return mSession.onInlineSuggestionsResponseLocked(autofillId, + new InlineSuggestionsResponse(Collections.EMPTY_LIST)); + } + return false; + } + + /** + * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused + * on the {@code autofillId}. + * + * @return false if there is no session, or if the IME callback is not available in the session. + */ + @GuardedBy("mLock") + boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, + @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { + // TODO(b/151123764): rename the method to better reflect what it does. + if (mSession != null) { + return mSession.onInlineSuggestionsResponseLocked(autofillId, + inlineSuggestionsResponse); + } + return false; + } +} diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java new file mode 100644 index 000000000000..ca230b6936ff --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.lang.ref.WeakReference; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Maintains an inline suggestion session with the IME. + * + * <p> Each session corresponds to one request from the Autofill manager service to create an + * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and + * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME. + */ +final class AutofillInlineSuggestionsRequestSession { + + private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName(); + private static final int INLINE_REQUEST_TIMEOUT_MS = 200; + + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + @NonNull + private final Handler mHandler; + @NonNull + private final Bundle mUiExtras; + + @GuardedBy("mLock") + @NonNull + private AutofillId mAutofillId; + @GuardedBy("mLock") + @Nullable + private Consumer<InlineSuggestionsRequest> mImeRequestConsumer; + + @GuardedBy("mLock") + private boolean mImeRequestReceived; + @GuardedBy("mLock") + @Nullable + private InlineSuggestionsRequest mImeRequest; + @GuardedBy("mLock") + @Nullable + private IInlineSuggestionsResponseCallback mResponseCallback; + @GuardedBy("mLock") + @Nullable + private Runnable mTimeoutCallback; + + @GuardedBy("mLock") + @Nullable + private AutofillId mImeCurrentFieldId; + @GuardedBy("mLock") + private boolean mImeInputStarted; + @GuardedBy("mLock") + private boolean mImeInputViewStarted; + @GuardedBy("mLock") + @Nullable + private InlineSuggestionsResponse mInlineSuggestionsResponse; + @GuardedBy("mLock") + private boolean mPreviousResponseIsNotEmpty; + + @GuardedBy("mLock") + private boolean mDestroyed = false; + + AutofillInlineSuggestionsRequestSession( + @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId, + @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock, + @NonNull AutofillId autofillId, + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mHandler = handler; + mLock = lock; + mUiExtras = uiExtras; + + mAutofillId = autofillId; + mImeRequestConsumer = requestConsumer; + } + + @GuardedBy("mLock") + @NonNull + AutofillId getAutofillIdLocked() { + return mAutofillId; + } + + /** + * Returns the {@link InlineSuggestionsRequest} provided by IME. + * + * <p> The caller is responsible for making sure Autofill hears back from IME before calling + * this method, using the {@link #mImeRequestConsumer}. + */ + @GuardedBy("mLock") + Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() { + if (mDestroyed) { + return Optional.empty(); + } + return Optional.ofNullable(mImeRequest); + } + + /** + * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused + * on the {@code autofillId}. + * + * @return false if the IME callback is not available. + */ + @GuardedBy("mLock") + boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, + @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { + if (mDestroyed) { + return false; + } + if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId); + if (mImeRequest == null || mResponseCallback == null) { + return false; + } + // TODO(b/151123764): each session should only correspond to one field. + mAutofillId = autofillId; + mInlineSuggestionsResponse = inlineSuggestionsResponse; + maybeUpdateResponseToImeLocked(); + return true; + } + + /** + * This method must be called when the session is destroyed, to avoid further callbacks from/to + * the IME. + */ + @GuardedBy("mLock") + void destroySessionLocked() { + mDestroyed = true; + } + + /** + * Requests the IME to create an {@link InlineSuggestionsRequest}. + * + * <p> This method should only be called once per session. + */ + @GuardedBy("mLock") + void onCreateInlineSuggestionsRequestLocked() { + if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId); + if (mDestroyed) { + return; + } + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId, + new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras), + new InlineSuggestionsRequestCallbackImpl(this)); + mTimeoutCallback = () -> { + Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest."); + handleOnReceiveImeRequest(null, null); + }; + mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS); + } + + /** + * Optionally sends inline response to the IME, depending on the current state. + */ + @GuardedBy("mLock") + private void maybeUpdateResponseToImeLocked() { + if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called"); + if (mDestroyed || mResponseCallback == null) { + return; + } + if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) { + // 1. if previous response is not empty, and IME just become invisible, then send + // empty response to make sure existing responses don't stick around on the IME. + // Although the inline suggestions should disappear when IME hides which removes them + // from the view hierarchy, but we still send an empty response to be extra safe. + + // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is + // fixed. + //if (sDebug) Log.d(TAG, "Send empty inline response"); + //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections + // .EMPTY_LIST)); + //mPreviousResponseIsNotEmpty = false; + } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId, + mImeCurrentFieldId)) { + // 2. if IME is visible, and response is not null, send the response + boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty(); + if (isEmptyResponse && !mPreviousResponseIsNotEmpty) { + // No-op if both the previous response and current response are empty. + return; + } + if (sDebug) { + Log.d(TAG, "Send inline response: " + + mInlineSuggestionsResponse.getInlineSuggestions().size()); + } + updateResponseToImeUncheckLocked(mInlineSuggestionsResponse); + // TODO(b/149945531): don't set the response to null so it's cached, once the bug is + // fixed. + mInlineSuggestionsResponse = null; + mPreviousResponseIsNotEmpty = !isEmptyResponse; + } + } + + /** + * Sends the {@code response} to the IME, assuming all the relevant checks are already done. + */ + @GuardedBy("mLock") + private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) { + if (mDestroyed) { + return; + } + try { + mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME"); + } + } + + /** + * Handles the {@code request} and {@code callback} received from the IME. + * + * <p> Should only invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request, + @Nullable IInlineSuggestionsResponseCallback callback) { + synchronized (mLock) { + if (mDestroyed || mImeRequestReceived) { + return; + } + mImeRequestReceived = true; + + if (mTimeoutCallback != null) { + if (sDebug) Log.d(TAG, "removing timeout callback"); + mHandler.removeCallbacks(mTimeoutCallback); + mTimeoutCallback = null; + } + if (request != null && callback != null) { + mImeRequest = request; + mResponseCallback = callback; + handleOnReceiveImeStatusUpdated(mAutofillId, true, false); + } + if (mImeRequestConsumer != null) { + // Note that mImeRequest is only set if both request and callback are non-null. + mImeRequestConsumer.accept(mImeRequest); + mImeRequestConsumer = null; + } + } + } + + /** + * Handles the IME status updates received from the IME. + * + * <p> Should only be invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted, + boolean imeInputViewStarted) { + synchronized (mLock) { + if (mDestroyed) { + return; + } + if (mImeCurrentFieldId != null) { + boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted); + boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted); + mImeInputStarted = imeInputStarted; + mImeInputViewStarted = imeInputViewStarted; + if (imeInputStartedChanged || imeInputViewStartedChanged) { + maybeUpdateResponseToImeLocked(); + } + } + } + } + + /** + * Handles the IME status updates received from the IME. + * + * <p> Should only be invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId, + boolean imeInputStarted, boolean imeInputViewStarted) { + synchronized (mLock) { + if (mDestroyed) { + return; + } + if (imeFieldId != null) { + mImeCurrentFieldId = imeFieldId; + } + handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted); + } + } + + private static final class InlineSuggestionsRequestCallbackImpl extends + IInlineSuggestionsRequestCallback.Stub { + + private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession; + + private InlineSuggestionsRequestCallbackImpl( + AutofillInlineSuggestionsRequestSession session) { + mSession = new WeakReference<>(session); + } + + @BinderThread + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called."); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session, + null, null)); + } + } + + @BinderThread + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) { + if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session, + request, callback)); + } + } + + @Override + public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, imeFieldId, true, false)); + } + } + + @Override + public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult); + } + } + + @BinderThread + @Override + public void onInputMethodStartInputView() { + if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, true, true)); + } + } + + @BinderThread + @Override + public void onInputMethodFinishInputView() { + if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, true, false)); + } + } + + @Override + public void onInputMethodFinishInput() throws RemoteException { + if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, false, false)); + } + } + } + + private static boolean match(@Nullable AutofillId autofillId, + @Nullable AutofillId imeClientFieldId) { + // The IME doesn't have information about the virtual view id for the child views in the + // web view, so we are only comparing the parent view id here. This means that for cases + // where there are two input fields in the web view, they will have the same view id + // (although different virtual child id), and we will not be able to distinguish them. + return autofillId != null && imeClientFieldId != null + && autofillId.getViewId() == imeClientFieldId.getViewId(); + } +} diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java deleted file mode 100644 index e2d511212a71..000000000000 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.autofill; - -import static com.android.server.autofill.Helper.sDebug; - -import android.annotation.BinderThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; -import android.util.Log; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.inputmethod.InlineSuggestionsRequest; -import android.view.inputmethod.InlineSuggestionsResponse; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.view.IInlineSuggestionsRequestCallback; -import com.android.internal.view.IInlineSuggestionsResponseCallback; -import com.android.internal.view.InlineSuggestionsRequestInfo; -import com.android.server.inputmethod.InputMethodManagerInternal; - -import java.util.Collections; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * Maintains an autofill inline suggestion session that communicates with the IME. - * - * <p> - * The same session may be reused for multiple input fields involved in the same autofill - * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one - * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline - * suggestions for different input fields. - * - * <p> - * This class is the sole place in Autofill responsible for directly communicating with the IME. It - * receives the IME input view start/finish events, with the associated IME field Id. It uses the - * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result, - * some of the response will be cached locally and only be sent when the IME is ready to show them. - * - * <p> - * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService - * side flow. - * - * <p> - * This class should hold the same lock as {@link Session} as they call into each other. - */ -final class InlineSuggestionSession { - - private static final String TAG = "AfInlineSuggestionSession"; - private static final int INLINE_REQUEST_TIMEOUT_MS = 200; - - @NonNull - private final InputMethodManagerInternal mInputMethodManagerInternal; - private final int mUserId; - @NonNull - private final ComponentName mComponentName; - @NonNull - private final Object mLock; - @NonNull - private final ImeStatusListener mImeStatusListener; - @NonNull - private final Handler mHandler; - - /** - * To avoid the race condition, one should not access {@code mPendingImeResponse} without - * holding the {@code mLock}. For consuming the existing value, tt's recommended to use - * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call. - */ - @GuardedBy("mLock") - @Nullable - private CompletableFuture<ImeResponse> mPendingImeResponse; - - @GuardedBy("mLock") - @Nullable - private AutofillResponse mPendingAutofillResponse; - - @GuardedBy("mLock") - private boolean mIsLastResponseNonEmpty = false; - - @Nullable - @GuardedBy("mLock") - private AutofillId mImeFieldId = null; - - @GuardedBy("mLock") - private boolean mImeInputViewStarted = false; - - InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, - int userId, ComponentName componentName, Handler handler, Object lock) { - mInputMethodManagerInternal = inputMethodManagerInternal; - mUserId = userId; - mComponentName = componentName; - mHandler = handler; - mLock = lock; - mImeStatusListener = new ImeStatusListener() { - @Override - public void onInputMethodStartInput(AutofillId imeFieldId) { - synchronized (mLock) { - mImeFieldId = imeFieldId; - mImeInputViewStarted = false; - } - } - - @Override - public void onInputMethodStartInputView() { - synchronized (mLock) { - mImeInputViewStarted = true; - AutofillResponse pendingAutofillResponse = mPendingAutofillResponse; - if (pendingAutofillResponse != null - && pendingAutofillResponse.mAutofillId.equalsIgnoreSession( - mImeFieldId)) { - mPendingAutofillResponse = null; - onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId, - pendingAutofillResponse.mResponse); - } - } - } - - @Override - public void onInputMethodFinishInputView() { - synchronized (mLock) { - mImeInputViewStarted = false; - } - } - - @Override - public void onInputMethodFinishInput() { - synchronized (mLock) { - mImeFieldId = null; - } - } - }; - } - - public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId, - @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) { - if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId); - - synchronized (mLock) { - // Clean up all the state about the previous request. - hideInlineSuggestionsUi(autofillId); - mImeFieldId = null; - mImeInputViewStarted = false; - if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) { - mPendingImeResponse.complete(null); - } - mPendingImeResponse = new CompletableFuture<>(); - // TODO(b/146454892): pipe the uiExtras from the ExtServices. - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( - mUserId, - new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()), - new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse, - mImeStatusListener, requestConsumer, mHandler, mLock)); - } - } - - public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() { - final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); - if (pendingImeResponse == null || !pendingImeResponse.isDone()) { - return Optional.empty(); - } - return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest); - } - - public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) { - synchronized (mLock) { - if (mIsLastResponseNonEmpty) { - return onInlineSuggestionsResponseLocked(autofillId, - new InlineSuggestionsResponse(Collections.EMPTY_LIST)); - } - return false; - } - } - - public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { - synchronized (mLock) { - return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse); - } - } - - private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { - final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse(); - if (completedImsResponse == null || !completedImsResponse.isDone()) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request"); - return false; - } - // There is no need to wait on the CompletableFuture since it should have been completed. - ImeResponse imeResponse = completedImsResponse.getNow(null); - if (imeResponse == null) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response"); - return false; - } - - // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we - // only compare the view id for now. - if (!mImeInputViewStarted || mImeFieldId == null - || autofillId.getViewId() != mImeFieldId.getViewId()) { - if (sDebug) { - Log.d(TAG, - "onInlineSuggestionsResponseLocked not sent because input view is not " - + "started for " + autofillId); - } - mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse); - // TODO(b/149442582): Although we are not sending the response to IME right away, we - // still return true to indicate that the response may be sent eventually, such that - // the dropdown UI will not be shown. This may not be the desired behavior in the - // auto-focus case where IME isn't shown after switching back to an activity. We may - // revisit this. - return true; - } - - try { - imeResponse.mCallback.onInlineSuggestionsResponse(autofillId, - inlineSuggestionsResponse); - mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty(); - if (sDebug) { - Log.d(TAG, "Autofill sends inline response to IME: " - + inlineSuggestionsResponse.getInlineSuggestions().size()); - } - return true; - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME"); - return false; - } - } - - @Nullable - @GuardedBy("mLock") - private CompletableFuture<ImeResponse> getPendingImeResponse() { - synchronized (mLock) { - return mPendingImeResponse; - } - } - - private static final class InlineSuggestionsRequestCallbackImpl - extends IInlineSuggestionsRequestCallback.Stub { - - private final Object mLock; - private final AutofillId mAutofillId; - @GuardedBy("mLock") - private final CompletableFuture<ImeResponse> mResponse; - @GuardedBy("mLock") - private final Consumer<InlineSuggestionsRequest> mRequestConsumer; - private final ImeStatusListener mImeStatusListener; - private final Handler mHandler; - private final Runnable mTimeoutCallback; - - private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId, - CompletableFuture<ImeResponse> response, - ImeStatusListener imeStatusListener, - Consumer<InlineSuggestionsRequest> requestConsumer, - Handler handler, Object lock) { - mAutofillId = autofillId; - mResponse = response; - mImeStatusListener = imeStatusListener; - mRequestConsumer = requestConsumer; - mLock = lock; - - mHandler = handler; - mTimeoutCallback = () -> { - Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest."); - completeIfNot(null); - }; - mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS); - } - - private void completeIfNot(@Nullable ImeResponse response) { - synchronized (mLock) { - if (mResponse.isDone()) { - return; - } - mResponse.complete(response); - mRequestConsumer.accept(response == null ? null : response.mRequest); - mHandler.removeCallbacks(mTimeoutCallback); - } - } - - @BinderThread - @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { - if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called."); - completeIfNot(null); - } - - @BinderThread - @Override - public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, - IInlineSuggestionsResponseCallback callback) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); - mImeStatusListener.onInputMethodStartInput(mAutofillId); - if (request != null && callback != null) { - completeIfNot(new ImeResponse(request, callback)); - } else { - completeIfNot(null); - } - } - - @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { - if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId); - mImeStatusListener.onInputMethodStartInput(imeFieldId); - } - - @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { - if (sDebug) { - Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult); - } - // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for - // IME to show. - } - - @BinderThread - @Override - public void onInputMethodStartInputView() { - if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received"); - mImeStatusListener.onInputMethodStartInputView(); - } - - @BinderThread - @Override - public void onInputMethodFinishInputView() { - if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received"); - mImeStatusListener.onInputMethodFinishInputView(); - } - - @Override - public void onInputMethodFinishInput() throws RemoteException { - if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received"); - mImeStatusListener.onInputMethodFinishInput(); - } - } - - private interface ImeStatusListener { - void onInputMethodStartInput(AutofillId imeFieldId); - - void onInputMethodStartInputView(); - - void onInputMethodFinishInputView(); - - void onInputMethodFinishInput(); - } - - /** - * A data class wrapping Autofill responses for the inline suggestion request. - */ - private static class AutofillResponse { - @NonNull - final AutofillId mAutofillId; - - @NonNull - final InlineSuggestionsResponse mResponse; - - AutofillResponse(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse response) { - mAutofillId = autofillId; - mResponse = response; - } - - } - - /** - * A data class wrapping IME responses for the create inline suggestions request. - */ - private static class ImeResponse { - @NonNull - final InlineSuggestionsRequest mRequest; - - @NonNull - final IInlineSuggestionsResponseCallback mCallback; - - ImeResponse(@NonNull InlineSuggestionsRequest request, - @NonNull IInlineSuggestionsResponseCallback callback) { - mRequest = request; - mCallback = callback; - } - - InlineSuggestionsRequest getRequest() { - return mRequest; - } - } -} diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 8b50b010a22c..b6bc7c5646a5 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -58,6 +58,7 @@ import com.android.internal.os.IResultReceiver; import com.android.server.autofill.ui.InlineSuggestionFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; @@ -263,6 +264,8 @@ final class RemoteAugmentedAutofillService && fieldIds.get(0).equals(focusedId); client.autofill(sessionId, fieldIds, dataset.getFieldValues(), hideHighlight); + inlineSuggestionsCallback.apply(new InlineSuggestionsResponse( + Collections.EMPTY_LIST)); } catch (RemoteException e) { Slog.w(TAG, "Encounter exception autofilling the values"); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java index 7ad5632dd70e..347174c4c804 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java @@ -27,7 +27,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteCallback; import android.service.autofill.IInlineSuggestionRenderService; import android.service.autofill.IInlineSuggestionUiCallback; import android.service.autofill.InlinePresentation; @@ -91,6 +93,15 @@ public final class RemoteInlineSuggestionRenderService extends hostInputToken, displayId)); } + /** + * Gets the inline suggestions renderer info as a {@link Bundle}. + */ + public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { + scheduleAsyncRequest((s) -> s.getInlineSuggestionsRendererInfo(new RemoteCallback( + (bundle) -> callback.sendResult(bundle) + ))); + } + @Nullable private static ServiceInfo getServiceInfo(Context context, int userId) { final String packageName = diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 12905696ff98..3d6861898aaf 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -304,7 +304,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean mForAugmentedAutofillOnly; @Nullable - private final InlineSuggestionSession mInlineSuggestionSession; + private final AutofillInlineSessionController mInlineSessionController; /** * Receiver of assist data from the app's {@link Activity}. @@ -652,10 +652,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mService.isInlineSuggestionsEnabled(); } - private boolean isInlineSuggestionRenderServiceAvailable() { - return mService.getRemoteInlineSuggestionRenderServiceLocked() != null; - } - /** * Clears the existing response for the partition, reads a new structure, and then requests a * new fill response from the fill service. @@ -715,13 +711,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Only ask IME to create inline suggestions request if Autofill provider supports it and // the render service is available. - if (isInlineSuggestionsEnabledByAutofillProviderLocked() - && isInlineSuggestionRenderServiceAvailable()) { + final RemoteInlineSuggestionRenderService remoteRenderService = + mService.getRemoteInlineSuggestionRenderServiceLocked(); + if (isInlineSuggestionsEnabledByAutofillProviderLocked() && remoteRenderService != null) { Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); if (inlineSuggestionsRequestConsumer != null) { - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - inlineSuggestionsRequestConsumer); + remoteRenderService.getInlineSuggestionsRendererInfo( + new RemoteCallback((extras) -> { + mInlineSessionController.onCreateInlineSuggestionsRequestLocked( + mCurrentViewId, inlineSuggestionsRequestConsumer, extras); + } + )); } } else { mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); @@ -777,8 +778,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); - mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, - componentName, handler, mLock); + mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, + userId, componentName, handler, mLock); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -1227,6 +1228,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } catch (RemoteException e) { Slog.e(TAG, "Error requesting to hide fill UI", e); } + + mInlineSessionController.hideInlineSuggestionsUiLocked(id); } } @@ -2561,7 +2564,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) Slog.v(TAG, "Exiting view " + id); mUi.hideFillUi(this); hideAugmentedAutofillLocked(viewState); - mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId); + mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); mCurrentViewId = null; } break; @@ -2779,7 +2782,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, @Nullable String filterText) { final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = - mInlineSuggestionSession.getInlineSuggestionsRequest(); + mInlineSessionController.getInlineSuggestionsRequestLocked(); if (!inlineSuggestionsRequest.isPresent()) { Log.w(TAG, "InlineSuggestionsRequest unavailable"); return false; @@ -2801,7 +2804,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId, this, () -> { synchronized (mLock) { - mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId); + mInlineSessionController.hideInlineSuggestionsUiLocked( + mCurrentViewId); } }, remoteRenderService); if (inlineSuggestionsResponse == null) { @@ -2809,7 +2813,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId, + return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId, inlineSuggestionsResponse); } @@ -3106,8 +3110,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState focusedId, currentValue, inlineSuggestionsRequest, /*inlineSuggestionsCallback=*/ - response -> mInlineSuggestionSession.onInlineSuggestionsResponse( - mCurrentViewId, response), + response -> { + synchronized (mLock) { + return mInlineSessionController + .onInlineSuggestionsResponseLocked( + mCurrentViewId, response); + } + }, /*onErrorCallback=*/ () -> { synchronized (mLock) { cancelAugmentedAutofillLocked(); @@ -3121,15 +3130,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // 1. the field is augmented autofill only (when standard autofill provider is None or // when it returns null response) // 2. standard autofill provider doesn't support inline suggestion - if (isInlineSuggestionRenderServiceAvailable() + final RemoteInlineSuggestionRenderService remoteRenderService = + mService.getRemoteInlineSuggestionRenderServiceLocked(); + if (remoteRenderService != null && (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledByAutofillProviderLocked())) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - /*requestConsumer=*/ requestAugmentedAutofill); + remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( + (extras) -> { + mInlineSessionController.onCreateInlineSuggestionsRequestLocked( + mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill, + extras); + }, mHandler)); } else { requestAugmentedAutofill.accept( - mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null)); + mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null)); } if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); diff --git a/services/core/Android.bp b/services/core/Android.bp index 5faed43dd6e6..a8bc2b4bc6ce 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -91,7 +91,6 @@ java_library_static { ], libs: [ - "android_system_server_stubs_current", "services.net", "android.hardware.light-V2.0-java", "android.hardware.power-java", @@ -101,6 +100,7 @@ java_library_static { "android.net.ipsec.ike.stubs.module_libs_api", "app-compat-annotations", "framework-tethering-stubs-module_libs_api", + "service-permission-stubs", ], required: [ diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java index aedafbbf1662..94f5741fe828 100644 --- a/services/core/java/android/os/UserManagerInternal.java +++ b/services/core/java/android/os/UserManagerInternal.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.pm.UserInfo; import android.graphics.Bitmap; +import com.android.server.pm.RestrictionsSet; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -57,21 +59,18 @@ public abstract class UserManagerInternal { * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set * restrictions enforced by the user. * - * @param originatingUserId user id of the user where the restriction originated. - * @param restrictions a bundle of user restrictions. - * @param restrictionOwnerType determines which admin {@code userId} corresponds to. - * The admin can be either - * {@link UserManagerInternal#OWNER_TYPE_DEVICE_OWNER}, - * {@link UserManagerInternal#OWNER_TYPE_PROFILE_OWNER}, - * {@link UserManagerInternal#OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE} - * or {@link UserManagerInternal#OWNER_TYPE_NO_OWNER}. - * If the admin is a DEVICE_OWNER or a PROFILE_OWNER_ORG_OWNED_DEVICE then - * a restriction may be applied globally depending on which restriction it is, - * otherwise it will be applied just on the current user. - * @see OwnerType + * @param originatingUserId user id of the user where the restrictions originated. + * @param global a bundle of global user restrictions. Global restrictions are + * restrictions that apply device-wide: to the managed profile, + * primary profile and secondary users and any profile created in + * any secondary user. + * @param local a restriction set of local user restrictions. The key is the user + * id of the user whom the restrictions are targeting. + * @param isDeviceOwner whether {@code originatingUserId} corresponds to device owner + * user id. */ public abstract void setDevicePolicyUserRestrictions(int originatingUserId, - @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType); + @Nullable Bundle global, @Nullable RestrictionsSet local, boolean isDeviceOwner); /** * Returns the "base" user restrictions. diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 192ea72224b1..3c0d880916ee 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -119,8 +119,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int MESSAGE_DISABLE = 2; private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3; private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4; - private static final int MESSAGE_REGISTER_ADAPTER = 20; - private static final int MESSAGE_UNREGISTER_ADAPTER = 21; private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30; private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31; private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40; @@ -642,10 +640,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Slog.w(TAG, "Callback is null in registerAdapter"); return null; } - Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER); - msg.obj = callback; - mHandler.sendMessage(msg); - + synchronized (mCallbacks) { + mCallbacks.register(callback); + } return mBluetooth; } @@ -655,9 +652,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return; } mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER); - msg.obj = callback; - mHandler.sendMessage(msg); + synchronized (mCallbacks) { + mCallbacks.unregister(callback); + } } public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { @@ -1559,18 +1556,20 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is up */ private void sendBluetoothServiceUpCallback() { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); + synchronized (mCallbacks) { + try { + int n = mCallbacks.beginBroadcast(); + Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers."); + for (int i = 0; i < n; i++) { + try { + mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); + } } + } finally { + mCallbacks.finishBroadcast(); } - } finally { - mCallbacks.finishBroadcast(); } } @@ -1578,18 +1577,20 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is down */ private void sendBluetoothServiceDownCallback() { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); + synchronized (mCallbacks) { + try { + int n = mCallbacks.beginBroadcast(); + Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers."); + for (int i = 0; i < n; i++) { + try { + mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); + } } + } finally { + mCallbacks.finishBroadcast(); } - } finally { - mCallbacks.finishBroadcast(); } } @@ -1917,17 +1918,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mContext.getPackageName()); } break; - - case MESSAGE_REGISTER_ADAPTER: { - IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - mCallbacks.register(callback); - break; - } - case MESSAGE_UNREGISTER_ADAPTER: { - IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - mCallbacks.unregister(callback); - break; - } case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2eaa766ad32d..8a1de1f0763e 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3153,7 +3153,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - nai.clatd.setNat64Prefix(prefix); + nai.clatd.setNat64PrefixFromDns(prefix); handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } @@ -5865,9 +5865,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull LinkProperties oldLp) { int netId = networkAgent.network.netId; - // The NetworkAgentInfo does not know whether clatd is running on its network or not, or - // whether there is a NAT64 prefix. Before we do anything else, make sure its LinkProperties - // are accurate. + // The NetworkAgent does not know whether clatd is running on its network or not, or whether + // a NAT64 prefix was discovered by the DNS resolver. Before we do anything else, make sure + // the LinkProperties for the network are accurate. networkAgent.clatd.fixupLinkProperties(oldLp, newLp); updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities, diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 8ccff7605811..7f25de6b3470 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -1836,12 +1836,13 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void requestLocationUpdates(LocationRequest request, ILocationListener listener, - PendingIntent intent, String packageName, String featureId) { + PendingIntent intent, String packageName, String featureId, String listenerId) { if (request == null) { request = DEFAULT_LOCATION_REQUEST; } - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId, + listenerId); identity.enforceLocationPermission(); WorkSource workSource = request.getWorkSource(); @@ -2027,7 +2028,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean getCurrentLocation(LocationRequest locationRequest, ICancellationSignal remoteCancellationSignal, ILocationListener listener, - String packageName, String featureId) { + String packageName, String featureId, String listenerId) { // side effect of validating locationRequest and packageName Location lastLocation = getLastLocation(locationRequest, packageName, featureId); if (lastLocation != null) { @@ -2052,7 +2053,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - requestLocationUpdates(locationRequest, listener, null, packageName, featureId); + requestLocationUpdates(locationRequest, listener, null, packageName, featureId, listenerId); CancellationSignal cancellationSignal = CancellationSignal.fromTransport( remoteCancellationSignal); if (cancellationSignal != null) { @@ -2598,8 +2599,6 @@ public class LocationManagerService extends ILocationManager.Stub { @Override @NonNull public List<LocationRequest> getTestProviderCurrentRequests(String provider) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, null); - LocationProviderManager manager = getLocationProviderManager(provider); if (manager == null) { throw new IllegalArgumentException("provider doesn't exist: " + provider); diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index d9e7c3851906..e77458cc955a 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -315,28 +315,31 @@ public class PackageWatchdog { // causing any elapsed time to be deducted from all existing packages before we add new // packages. This maintains the invariant that the elapsed time for ALL (new and existing) // packages is the same. - syncState("observing new packages"); + mLongTaskHandler.post(() -> { + syncState("observing new packages"); - synchronized (mLock) { - ObserverInternal oldObserver = mAllObservers.get(observer.getName()); - if (oldObserver == null) { - Slog.d(TAG, observer.getName() + " started monitoring health " - + "of packages " + packageNames); - mAllObservers.put(observer.getName(), - new ObserverInternal(observer.getName(), packages)); - } else { - Slog.d(TAG, observer.getName() + " added the following " - + "packages to monitor " + packageNames); - oldObserver.updatePackagesLocked(packages); + synchronized (mLock) { + ObserverInternal oldObserver = mAllObservers.get(observer.getName()); + if (oldObserver == null) { + Slog.d(TAG, observer.getName() + " started monitoring health " + + "of packages " + packageNames); + mAllObservers.put(observer.getName(), + new ObserverInternal(observer.getName(), packages)); + } else { + Slog.d(TAG, observer.getName() + " added the following " + + "packages to monitor " + packageNames); + oldObserver.updatePackagesLocked(packages); + } } - } - // Register observer in case not already registered - registerHealthObserver(observer); + // Register observer in case not already registered + registerHealthObserver(observer); + + // Sync after we add the new packages to the observers. We may have received packges + // requiring an earlier schedule than we are currently scheduled for. + syncState("updated observers"); + }); - // Sync after we add the new packages to the observers. We may have received packges - // requiring an earlier schedule than we are currently scheduled for. - syncState("updated observers"); } /** diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 067147703b0c..b0a586d122ea 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -220,6 +220,10 @@ class StorageManagerService extends IStorageManager.Stub private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage(); + // A system property to control if obb app data isolation is enabled in vold. + private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.sys.vold_app_data_isolation_enabled"; + /** * If {@code 1}, enables the isolated storage feature. If {@code -1}, * disables the isolated storage feature. If {@code 0}, uses the default @@ -596,6 +600,8 @@ class StorageManagerService extends IStorageManager.Stub private final boolean mIsFuseEnabled; + private final boolean mVoldAppDataIsolationEnabled; + @GuardedBy("mLock") private final Set<Integer> mUidsWithLegacyExternalStorage = new ArraySet<>(); // Not guarded by lock, always used on the ActivityManager thread @@ -1516,7 +1522,7 @@ class StorageManagerService extends IStorageManager.Stub if (vol.type == VolumeInfo.TYPE_EMULATED) { if (newState != VolumeInfo.STATE_MOUNTED) { mFuseMountedUser.remove(vol.getMountUserId()); - } else { + } else if (mVoldAppDataIsolationEnabled){ final int userId = vol.getMountUserId(); mFuseMountedUser.add(userId); // Async remount app storage so it won't block the main thread. @@ -1740,6 +1746,8 @@ class StorageManagerService extends IStorageManager.Stub // incorrect until #updateFusePropFromSettings where we set the correct value and reboot if // different mIsFuseEnabled = SystemProperties.getBoolean(PROP_FUSE, DEFAULT_FUSE_ENABLED); + mVoldAppDataIsolationEnabled = mIsFuseEnabled && SystemProperties.getBoolean( + ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); mContext = context; mResolver = mContext.getContentResolver(); mCallbacks = new Callbacks(FgThread.get().getLooper()); @@ -4468,9 +4476,8 @@ class StorageManagerService extends IStorageManager.Stub String.format("/storage/emulated/%d/Android/data/%s/", userId, pkg); - int appUid = - UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid()); // Create package obb and data dir if it doesn't exist. + int appUid = UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid()); File file = new File(packageObbDir); if (!file.exists()) { vold.setupAppDir(packageObbDir, appUid); diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 061ff42de60b..58972a5346c7 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -123,6 +123,8 @@ public class Watchdog extends Thread { "android.hardware.neuralnetworks@1.0::IDevice", "android.hardware.power.stats@1.0::IPowerStats", "android.hardware.sensors@1.0::ISensors", + "android.hardware.sensors@2.0::ISensors", + "android.hardware.sensors@2.1::ISensors", "android.hardware.vr@1.0::IVr", "android.system.suspend@1.0::ISystemSuspend" ); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7523710dde9f..21760cdf02eb 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1440,7 +1440,7 @@ public final class ActiveServices { active.mPackageName = r.packageName; active.mUid = r.appInfo.uid; active.mShownWhileScreenOn = mScreenOn; - if (r.app != null) { + if (r.app != null && r.app.uidRecord != null) { active.mAppOnTop = active.mShownWhileTop = r.app.uidRecord.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 62d7eb1d0448..e41ba0e1745d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12121,8 +12121,6 @@ public class ActivityManagerService extends IActivityManager.Stub void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { - boolean needSep = false; - pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)"); boolean printed = false; if (mAllowedAssociations != null) { @@ -12130,21 +12128,16 @@ public class ActivityManagerService extends IActivityManager.Stub final String pkg = mAllowedAssociations.keyAt(i); final ArraySet<String> asc = mAllowedAssociations.valueAt(i).getAllowedPackageAssociations(); - boolean printedHeader = false; + if (!printed) { + pw.println(" Allowed associations (by restricted package):"); + printed = true; + } + pw.print(" * "); + pw.print(pkg); + pw.println(":"); for (int j = 0; j < asc.size(); j++) { if (dumpPackage == null || pkg.equals(dumpPackage) || asc.valueAt(j).equals(dumpPackage)) { - if (!printed) { - pw.println(" Allowed associations (by restricted package):"); - printed = true; - needSep = true; - } - if (!printedHeader) { - pw.print(" * "); - pw.print(pkg); - pw.println(":"); - printedHeader = true; - } pw.print(" Allow: "); pw.println(asc.valueAt(j)); } diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java index bc455338fc52..8f8990fdaae7 100644 --- a/services/core/java/com/android/server/am/AnrHelper.java +++ b/services/core/java/com/android/server/am/AnrHelper.java @@ -98,7 +98,7 @@ class AnrHelper { final long endTime = SystemClock.uptimeMillis(); Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in " + (endTime - startTime) + "ms, latency " + reportLatency - + (onlyDumpSelf ? "ms" : "ms (expired, only dump ANR app)")); + + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms")); } mRunning.set(false); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index dbcb3da3e2f4..2d6ef81faf1c 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -231,7 +231,8 @@ public final class OomAdjuster { final ServiceThread adjusterThread = new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST, false /* allowIo */); adjusterThread.start(); - Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP); + adjusterThread.getThreadHandler().post(() -> Process.setThreadGroupAndCpuset( + adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP)); return adjusterThread; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md new file mode 100644 index 000000000000..eda511ab7369 --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjuster.md @@ -0,0 +1,129 @@ +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Oom Adjuster Designs + +## Purpose of Oom Adjuster + +The Android OS runs with limited hardware resources, i.e. CPU/RAM/Power. To strive for the better performance, Oom Ajuster is introduced to tweak the following 3 major factors: + + * Process State + * Wildly used by the System Server, i.e., determine if it's foreground or not, change the GC behavior, etc. + * Defined in `ActivityManager#PROCESS_STATE_*` + * Oom Adj score + * Used by the lmkd to determine which process should be expunged on memory pressure. + * Defined in `ProcessList#*_ADJ` + * Scheduler Group + * Used to tweak the process group, thread priorities. + * Top process is scheduled to be running on a dedicated big core, while foreground processes take the other big cores; background processes stay with LITTLE cores instead. + +## Process Capabilities + +Besides the above 3 major factors, Android R introduced the Process Capabilities `ActivityManager#PROCESS_CAPABILITY_*`. It's a new attribute to process record, mainly designed for supporting the "while-in-use" permission model - in additional to the traditional Android permissions, wheather or not a process has access to a given API, will be guarded by its current process state as well. The OomAdjuster will compute the process capabilities during updating the oom adj. Meanwhile, the flag `ActivityManager#BIND_INCLUDE_CAPABILITIES` enables to possiblity to "transfer" the capability from a client process to the service process it binds to. + +## Rationale of Oom Adjuster + +System server keeps a list of recent used app processes. Given the 4 types of entities that an Android processes could have: Activity, Service, Content Provider and Broadcast Receiver, the System Server has to adjust the above 3 factors to give the users the best performance according to the states of the entities. A typical case would be that: foreground app A binds into a background service B in order to serve the user, in the case of memory pressure, the background service B should be avoided from being expunged since it would result user-perceptible interruption of service. The Oom Adjuster is to tweak the aforementioned 3 factors for those app processes. + +The timing of updating the Oom Adj score is vital: assume a camera process in background gets launched into foreground, launching camera typically incurs high memory pressure, which could incur low memory kills - if the camera process isn't moved out of the background adj group, it could get killed by lmkd. Therefore the updates have to be called pretty frequently: in case there is an activity start, service binding, etc. + +The update procedure basically consists of 3 parts: + * Find out the process record to be updated + * There are two categories of updateOomAdjLocked: one with the target process record to be updated, while the other one is to update all process record. + * Besides that, while computing the Oom Aj score, the clients of service connections or content providers of the present process record, which forms a process dependency graph actually, will be evaluated as well. + * Starting from Android R, when updating for a specific process record, an optimization is made that, only the reachable process records starting from this process record in the process dependency graph, will be re-evaluated. + * The `cached` Oom Adj scores are grouped in `bucket`, which is used in the isolated processes: they could be correlated - assume one isolated Chrome process is at Oom Adj score 920 and another one is 980; the later one could get expunged much earlier than the former one, which doesn't make sense; grouping them would be a big relief for this case. + * Compute Oom Adj score + * This procedure returns true if there is a score change, false if there is no. + * The curAdj field in the process record is used as an intermediate value during the computation. + * Initialize the Process State to `PROCESS_STATE_CACHED_EMPTY`, which is the lowest importance. + * Calculate the scores based on various factors: + * If it's not allowed to be lower than `ProcessList#FOREGROUND_APP_ADJ`, meaning it's propbably a persistent process, there is no too much to do here. + * Exame if the process is the top app, running remote animation, running instrumentation, receiving broadcast, executing services, running on top but sleeping (screen off), update the intermediate values. + * Ask Window Manager (yes, ActivityTaskManager is with WindowManager now) to tell each activity's visibility information. + * Check if the process has recent tasks, check if it's hosting a foreground service, overlay UI, toast etc. Note for the foreground service, if it was in foreground status, allow it to stay in higher rank in memory for a while: Assuming a camera captureing case, where the camera app is still processing the picture while being switched out of foreground - keep it stay in higher rank in memory would ensure the pictures are persisted correctly. + * Check if the process is the heavy weight process, whose launching/exiting would be slow and it's better to keep it in the memory. Note there should be only one heavy weight process across the system. + * For sure the Home process shouldn't be expunged frequently as well. + * The next two factors are either it was the previous process with visible UI to the user, or it's a backup agent. + * And then it goes to the massive searches against the service connections and the content providers, each of the clients will be evaluated, and the Oom Adj score could get updated according to its clients' scores. However there are a bunch of service binding flags which could impact the result: + * Below table captures the results with given various service binding states: + | Conditon #1 | Condition #2 | Condition #3 | Condition #4 | Result | + |---------------------------------|------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|--------------------------| + | `BIND_WAIVE_PRIORITY` not set | `BIND_ALLOW_OOM_MANAGEMENT` set | Shown UI && Not Home | | Use the app's own Adj | + | | | Inactive for a while | | Use the app's own Adj | + | | Client has a higher importance | Shown UI && Not Home && client is invisible | | Use the app's own Adj | + | | | `BIND_ABOVE_CLIENT` and `BIND_IMPORTANT` set | Client is not persistent | Try client's Adj | + | | | | Client is persistent | Try persistent Adj | + | | | `BIND_NOT_PERCEPTIBLE` set | client < perceptible && app > low perceptible | Try low perceptible Adj | + | | | `BIND_NOT_VISIBLE` set | client < perceptible && app > perceptible | Try perceptible Adj | + | | | Client >= perceptible | | Try client's Adj | + | | | Adj > visible | | Max of client/Own Adj | + | | | | | Use the app's own Adj | + | | `BIND_NOT_FOREGROUND`+`BIND_IMPORTANT_BACKGROUND` not set | Client's sched group > app's | `BIND_IMPORTANT` is set | Use client's sched group | + | | | | | Use default sched group | + | | | Client's process state < top | `BIND_FOREGROUND_SERVICE` is set | ProcState = bound fg | + | | | | `BIND_FOREGROUND_SERVICE_WHILE_AWAKE` + screen ON | ProcState = bound fg | + | | | | | ProcState = important fg | + | | | Client's process state = top | | ProcState = bound top | + | | `BIND_IMPORTANT_BACKGROUND` not set | Client's process state < transient bg | | ProcState = transient bg | + | | `BIND_NOT_FOREGROUND` or `BIND_IMPORTANT_BACKGROUND` set | Client's process state < important bg | | ProcState = important bg | + | `BIND_ADJUST_WITH_ACTIVITY` set | Adj > fg && App visible | | | Adj = foreground | + | | | `BIND_NOT_FOREGROUND` not set | `BIND_IMPORTANT` is set | Sched = top app bound | + | | | | `BIND_IMPORTANT` is NOT set | Sched = default | + * Below table captures the results with given various content provider binding states: + | Conditon #1 | Condition #2 | Condition #3 | Result | + |---------------------------------|------------------------------------------------------------|----------------------------------------------|--------------------------| + | Client's process state >= cached| | | Client ProcState = empty | + | Adj > Client Adj | Not shown UI or is Home, or Client's Adj <= perceptible | Client's Adj <= foreground Adj | Try foreground Adj | + | | | Client's Adj > foreground Adj | Try client's Adj | + | Client's process state <= fg svc| Client's process state is top | | ProcState = bound top | + | | Client's process state is NOT top | | ProcState = bound fg svc | + | Has external dependencies | Adj > fg app | | adj = fg app | + | | Process state > important foreground | | ProcState = important fg | + | Still within retain time | Adj > previous app Adj | | adj = previuos app adj | + | | Process state > last activity | | ProcState = last activity| + * Some additional tweaks after the above ones: + | Conditon #1 | Condition #2 | Condition #3 | Result | + |---------------------------------|------------------------------------------------------------|----------------------------------------------|------------------------------------| + | Process state >= cached empty | Has client activities | | ProcState = cached activity client | + | | treat like activity (IME) | | ProcState = cached activity | + | Adj is service adj | computing all process records | Num of new service A > 1/3 of services | Push it to service B | + | | | Low on RAM and app process's PSS is large | Push it to service B | + * Apply the scores, which consists of: write into kernel sysfs entries to update the Oom Adj scores; call kernel API to set the thread priorities, and then tell the world the new process state + +## Cycles, Cycles, Cycles + +Another interesting aspect of the Oom Adjuster is the cycles of the dependencies. A simple example would be like below illustration, process A is hosting a service which is bound by process B; meanwhile the process B is hosting a service which is bound by process A. +<pre> + +-------------+ +-------------+ + | Process A | <-------- | Process B | + | (service 1) | --------> | (service 2) | + +-------------+ +-------------+ +</pre> + +There could be very complicated cases, which could involve multiple cycles, and in the dependency graph, each of the process record node could have different importance. +<pre> + +-------------+ +-------------+ +-------------+ +-------------+ +-------------+ + | Process D | --------> | Process A | <-------- | Process B | <-------- | Process C | <-------- | Process A | + | | | (service 1) | | (service 2) | | (service 3) | | (service 1) | + +-------------+ +-------------+ +-------------+ +-------------+ +-------------+ +</pre> + +The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current Oom Adjuster calling. And each of the process record has a field to track in which sequence the process record is evaluated. If during the Oom Adj computation, a process record with sequence ID as same as the current global sequence ID, this would mean that a cycle is detected; in this case: + * Decrement the sequence ID of each process if there is a cycle. + * Re-evaluate each of the process record within the cycle until nothing was promoted. + * Iterate the processes from least important to most important ones. + * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3. + diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 595275d20154..89fa02bbbd64 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -97,6 +97,7 @@ import android.os.storage.StorageManagerInternal; import android.system.Os; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; import android.util.LongSparseArray; import android.util.Pair; @@ -137,6 +138,7 @@ import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Activity manager code dealing with processes. @@ -152,6 +154,9 @@ public final class ProcessList { static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = "persist.sys.vold_app_data_isolation_enabled"; + // A system property to control if fuse is enabled. + static final String ANDROID_FUSE_ENABLED = "persist.sys.fuse"; + // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. static final int MIN_CRASH_INTERVAL = 60 * 1000; @@ -705,8 +710,13 @@ public final class ProcessList { // want some apps enabled while some apps disabled mAppDataIsolationEnabled = SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true); - mVoldAppDataIsolationEnabled = SystemProperties.getBoolean( + boolean fuseEnabled = SystemProperties.getBoolean(ANDROID_FUSE_ENABLED, false); + boolean voldAppDataIsolationEnabled = SystemProperties.getBoolean( ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); + if (!fuseEnabled && voldAppDataIsolationEnabled) { + Slog.e(TAG, "Fuse is not enabled while vold app data isolation is enabled"); + } + mVoldAppDataIsolationEnabled = fuseEnabled && voldAppDataIsolationEnabled; mAppDataIsolationWhitelistedApps = new ArrayList<>( SystemConfig.getInstance().getAppDataIsolationWhitelistedApps()); @@ -1846,11 +1856,13 @@ public final class ProcessList { runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; } - // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level, - // or the compat feature. - if (app.info.allowsNativeHeapPointerTagging() - && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { - runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + if (Zygote.nativeSupportsTaggedPointers()) { + // Enable heap pointer tagging if supported by the kernel, unless disabled by the + // app manifest, target sdk level, or compat feature. + if (app.info.allowsNativeHeapPointerTagging() + && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { + runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } } runtimeFlags |= decideGwpAsanLevel(app); @@ -2127,18 +2139,11 @@ public final class ProcessList { for (String packageName : packages) { String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid(); long inode = pmInt.getCeDataInode(packageName, userId); - if (inode != 0) { - result.put(packageName, Pair.create(volumeUuid, inode)); - } - } - if (mAppDataIsolationWhitelistedApps != null) { - for (String packageName : mAppDataIsolationWhitelistedApps) { - String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid(); - long inode = pmInt.getCeDataInode(packageName, userId); - if (inode != 0) { - result.put(packageName, Pair.create(volumeUuid, inode)); - } + if (inode == 0) { + Slog.w(TAG, packageName + " inode == 0 (b/152760674)"); + return null; } + result.put(packageName, Pair.create(volumeUuid, inode)); } return result; @@ -2160,35 +2165,56 @@ public final class ProcessList { app.setHasForegroundActivities(true); } - StorageManagerInternal storageManagerInternal = LocalServices.getService( - StorageManagerInternal.class); final Map<String, Pair<String, Long>> pkgDataInfoMap; + final Map<String, Pair<String, Long>> whitelistedAppDataInfoMap; boolean bindMountAppStorageDirs = false; + boolean bindMountAppsData = mAppDataIsolationEnabled + && UserHandle.isApp(app.uid) + && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info); - if (mAppDataIsolationEnabled && UserHandle.isApp(app.uid) - && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info)) { - // Get all packages belongs to the same shared uid. sharedPackages is empty array - // if it doesn't have shared uid. - final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked(); - final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage( - app.info.packageName, app.userId); - pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0 - ? new String[]{app.info.packageName} : sharedPackages, uid); - - int userId = UserHandle.getUserId(uid); - if (mVoldAppDataIsolationEnabled - && !storageManagerInternal.isExternalStorageService(uid)) { - bindMountAppStorageDirs = true; - if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(), - app.processName)) { - // Cannot prepare Android/app and Android/obb directory, - // so we won't mount it in zygote. - app.bindMountPending = true; - bindMountAppStorageDirs = false; - } + // Get all packages belongs to the same shared uid. sharedPackages is empty array + // if it doesn't have shared uid. + final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked(); + final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage( + app.info.packageName, app.userId); + final String[] targetPackagesList = sharedPackages.length == 0 + ? new String[]{app.info.packageName} : sharedPackages; + + pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid); + if (pkgDataInfoMap == null) { + // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a + // tmp free pass. + bindMountAppsData = false; + } + + // Remove all packages in pkgDataInfoMap from mAppDataIsolationWhitelistedApps, so + // it won't be mounted twice. + final Set<String> whitelistedApps = new ArraySet<>(mAppDataIsolationWhitelistedApps); + for (String pkg : targetPackagesList) { + whitelistedApps.remove(pkg); + } + + whitelistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt, + whitelistedApps.toArray(new String[0]), uid); + if (whitelistedAppDataInfoMap == null) { + // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a + // tmp free pass. + bindMountAppsData = false; + } + + int userId = UserHandle.getUserId(uid); + StorageManagerInternal storageManagerInternal = LocalServices.getService( + StorageManagerInternal.class); + if (mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid) + && !storageManagerInternal.isExternalStorageService(uid)) { + bindMountAppStorageDirs = true; + if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(), + app.processName)) { + // Cannot prepare Android/app and Android/obb directory, + // so we won't mount it in zygote. + app.bindMountPending = true; + bindMountAppStorageDirs = false; } - } else { - pkgDataInfoMap = null; } final Process.ProcessStartResult startResult; @@ -2206,7 +2232,8 @@ public final class ProcessList { app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, app.info.packageName, /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp, - app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs, + app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap, + bindMountAppsData, bindMountAppStorageDirs, new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, @@ -2214,7 +2241,7 @@ public final class ProcessList { app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags, isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap, - bindMountAppStorageDirs, + whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs, new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } checkSlow(startTime, "startProcess: returned from zygote!"); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 30e765f3d602..02d8571c2dcd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3622,7 +3622,15 @@ public class AudioService extends IAudioService.Stub hdlr = h; // Remove from client list so that it is re-inserted at top of list iter.remove(); - hdlr.getBinder().unlinkToDeath(hdlr, 0); + try { + hdlr.getBinder().unlinkToDeath(hdlr, 0); + if (cb != hdlr.getBinder()) { + hdlr = null; + } + } catch (NoSuchElementException e) { + hdlr = null; + Log.w(TAG, "link does not exist ..."); + } break; } } diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 4431abe43136..808f8c21cc8d 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -558,8 +558,10 @@ public abstract class BiometricServiceBase extends SystemService FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT); + ClientMonitor newClient = mPendingClient; mCurrentClient = null; - startClient(mPendingClient, false); + mPendingClient = null; + startClient(newClient, 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 3ecf87c6860f..ad73b6491697 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -382,8 +382,10 @@ public class FaceService extends BiometricServiceBase { checkPermission(MANAGE_BIOMETRIC); updateActiveGroup(userId, opPackageName); - mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, - UserHandle.CURRENT); + mHandler.post(() -> { + mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, + UserHandle.CURRENT); + }); final boolean restricted = isRestricted(); final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 82465f8a093e..741cb5b41ea3 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -81,7 +81,7 @@ public class Nat464Xlat extends BaseNetworkObserver { RUNNING, // start() called, and the stacked iface is known to be up. } - private IpPrefix mNat64Prefix; + private IpPrefix mNat64PrefixFromDns; private String mBaseIface; private String mIface; private Inet6Address mIPv6Address; @@ -100,7 +100,7 @@ public class Nat464Xlat extends BaseNetworkObserver { * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to * enable NAT64 prefix discovery. * - * @param network the NetworkAgentInfo corresponding to the network. + * @param nai the NetworkAgentInfo corresponding to the network. * @return true if the network requires clat, false otherwise. */ @VisibleForTesting @@ -180,7 +180,7 @@ public class Nat464Xlat extends BaseNetworkObserver { String addrStr = null; try { - addrStr = mNetd.clatdStart(baseIface, mNat64Prefix.toString()); + addrStr = mNetd.clatdStart(baseIface, getNat64Prefix().toString()); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e); } @@ -213,12 +213,10 @@ public class Nat464Xlat extends BaseNetworkObserver { } mIface = null; mBaseIface = null; - mState = State.IDLE; if (requiresClat(mNetwork)) { mState = State.DISCOVERING; } else { stopPrefixDiscovery(); - mState = State.IDLE; } } @@ -285,6 +283,7 @@ public class Nat464Xlat extends BaseNetworkObserver { private void stopPrefixDiscovery() { try { mDnsResolver.stopPrefix64Discovery(getNetId()); + mState = State.IDLE; } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e); } @@ -294,32 +293,52 @@ public class Nat464Xlat extends BaseNetworkObserver { * Starts/stops NAT64 prefix discovery and clatd as necessary. */ public void update() { - // TODO: turn this class into a proper StateMachine. // http://b/126113090 - if (requiresClat(mNetwork)) { - if (!isPrefixDiscoveryStarted()) { - startPrefixDiscovery(); - } else if (shouldStartClat(mNetwork)) { - // NAT64 prefix detected. Start clatd. - // TODO: support the NAT64 prefix changing after it's been discovered. There is no - // need to support this at the moment because it cannot happen without changes to - // the Dns64Configuration code in netd. - start(); - } else { - // NAT64 prefix removed. Stop clatd and go back into DISCOVERING state. - stop(); - } - } else { - // Network no longer requires clat. Stop clat and prefix discovery. - if (isStarted()) { - stop(); - } else if (isPrefixDiscoveryStarted()) { - leaveStartedState(); - } + // TODO: turn this class into a proper StateMachine. http://b/126113090 + switch (mState) { + case IDLE: + if (requiresClat(mNetwork)) { + // Network is detected to be IPv6-only. + // TODO: consider going to STARTING directly if the NAT64 prefix is already + // known. This would however result in clatd running without prefix discovery + // running, which might be a surprising combination. + startPrefixDiscovery(); // Enters DISCOVERING state. + return; + } + break; + + case DISCOVERING: + if (shouldStartClat(mNetwork)) { + // NAT64 prefix detected. Start clatd. + start(); // Enters STARTING state. + return; + } + if (!requiresClat(mNetwork)) { + // IPv4 address added. Go back to IDLE state. + stopPrefixDiscovery(); + return; + } + break; + + case STARTING: + case RUNNING: + // NAT64 prefix removed, or IPv4 address added. + // Stop clatd and go back into DISCOVERING or idle. + if (!shouldStartClat(mNetwork)) { + stop(); + } + break; + // TODO: support the NAT64 prefix changing after it's been discovered. There is + // no need to support this at the moment because it cannot happen without + // changes to the Dns64Configuration code in netd. } } - public void setNat64Prefix(IpPrefix nat64Prefix) { - mNat64Prefix = nat64Prefix; + private IpPrefix getNat64Prefix() { + return mNat64PrefixFromDns; + } + + public void setNat64PrefixFromDns(IpPrefix prefix) { + mNat64PrefixFromDns = prefix; } /** @@ -328,7 +347,7 @@ public class Nat464Xlat extends BaseNetworkObserver { * has no idea that 464xlat is running on top of it. */ public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) { - lp.setNat64Prefix(mNat64Prefix); + lp.setNat64Prefix(getNat64Prefix()); if (!isRunning()) { return; diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 61b18eedfc6e..9a910bf5e859 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -80,6 +80,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -398,78 +399,93 @@ public final class ContentService extends IContentService.Stub { */ @Override public void notifyChange(Uri[] uris, IContentObserver observer, - boolean observerWantsSelfNotifications, int flags, int userHandle, + boolean observerWantsSelfNotifications, int flags, int userId, int targetSdkVersion, String callingPackage) { - final ObserverCollector collector = new ObserverCollector(); - for (Uri uri : uris) { - notifyChange(uri, observer, observerWantsSelfNotifications, flags, userHandle, - targetSdkVersion, callingPackage, collector); - } - final long token = clearCallingIdentity(); - try { - collector.dispatch(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - public void notifyChange(Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, int flags, int userHandle, - int targetSdkVersion, String callingPackage, ObserverCollector collector) { - if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle - + " from observer " + observer + ", flags " + Integer.toHexString(flags)); - - if (uri == null) { - throw new NullPointerException("Uri must not be null"); + if (DEBUG) { + Slog.d(TAG, "Notifying update of " + Arrays.toString(uris) + " for user " + userId + + ", observer " + observer + ", flags " + Integer.toHexString(flags)); } final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); - final int callingUserHandle = UserHandle.getCallingUserId(); + final int callingUserId = UserHandle.getCallingUserId(); + + // Set of notification events that we need to dispatch + final ObserverCollector collector = new ObserverCollector(); - userHandle = handleIncomingUser(uri, callingPid, callingUid, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true, userHandle); + // Set of content provider authorities that we've validated the caller + // has access to, mapped to the package name hosting that provider + final ArrayMap<Pair<String, Integer>, String> validatedProviders = new ArrayMap<>(); - final String msg = LocalServices.getService(ActivityManagerInternal.class) - .checkContentProviderAccess(uri.getAuthority(), userHandle); - if (msg != null) { - if (targetSdkVersion >= Build.VERSION_CODES.O) { - throw new SecurityException(msg); - } else { - if (msg.startsWith("Failed to find provider")) { - // Sigh, we need to quietly let apps targeting older API - // levels notify on non-existent providers. - } else { - Log.w(TAG, "Ignoring notify for " + uri + " from " + callingUid + ": " + msg); - return; + for (Uri uri : uris) { + // Validate that calling app has access to this provider + final int resolvedUserId = handleIncomingUser(uri, callingPid, callingUid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true, userId); + final Pair<String, Integer> provider = Pair.create(uri.getAuthority(), resolvedUserId); + if (!validatedProviders.containsKey(provider)) { + final String msg = LocalServices.getService(ActivityManagerInternal.class) + .checkContentProviderAccess(uri.getAuthority(), resolvedUserId); + if (msg != null) { + if (targetSdkVersion >= Build.VERSION_CODES.O) { + throw new SecurityException(msg); + } else { + if (msg.startsWith("Failed to find provider")) { + // Sigh, we need to quietly let apps targeting older API + // levels notify on non-existent providers. + } else { + Log.w(TAG, "Ignoring notify for " + uri + " from " + + callingUid + ": " + msg); + continue; + } + } } + + // Remember that we've validated this access + final String packageName = getProviderPackageName(uri, resolvedUserId); + validatedProviders.put(provider, packageName); } - } - // This makes it so that future permission checks will be in the context of this - // process rather than the caller's process. We will restore this before returning. - long identityToken = clearCallingIdentity(); - try { + // No concerns raised above, so caller has access; let's collect the + // notifications that should be dispatched synchronized (mRootNode) { - mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, - flags, userHandle, collector); + final int segmentCount = ObserverNode.countUriSegments(uri); + mRootNode.collectObserversLocked(uri, segmentCount, 0, observer, + observerWantsSelfNotifications, flags, resolvedUserId, collector); } - if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) { - SyncManager syncManager = getSyncManager(); - if (syncManager != null) { - syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, - callingUid, - uri.getAuthority(), getSyncExemptionForCaller(callingUid), - callingUid, callingPid, callingPackage); + } + + final long token = clearCallingIdentity(); + try { + // Actually dispatch all the notifications we collected + collector.dispatch(); + + for (int i = 0; i < validatedProviders.size(); i++) { + final String authority = validatedProviders.keyAt(i).first; + final int resolvedUserId = validatedProviders.keyAt(i).second; + final String packageName = validatedProviders.valueAt(i); + + // Kick off sync adapters for any authorities we touched + if ((flags & ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.scheduleLocalSync(null /* all accounts */, callingUserId, + callingUid, + authority, getSyncExemptionForCaller(callingUid), + callingUid, callingPid, callingPackage); + } } - } - synchronized (mCache) { - final String providerPackageName = getProviderPackageName(uri, userHandle); - invalidateCacheLocked(userHandle, providerPackageName, uri); + // Invalidate caches for any authorities we touched + synchronized (mCache) { + for (Uri uri : uris) { + if (Objects.equals(uri.getAuthority(), authority)) { + invalidateCacheLocked(resolvedUserId, packageName, uri); + } + } + } } } finally { - restoreCallingIdentity(identityToken); + Binder.restoreCallingIdentity(token); } } @@ -553,9 +569,10 @@ public final class ContentService extends IContentService.Stub { // Immediately dispatch notifications to foreground apps that // are important to the user; all other background observers are // delayed to avoid stampeding + final boolean noDelay = (key.flags & ContentResolver.NOTIFY_NO_DELAY) != 0; final int procState = LocalServices.getService(ActivityManagerInternal.class) .getUidProcessState(key.uid); - if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || noDelay) { task.run(); } else { BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY); @@ -1533,7 +1550,7 @@ public final class ContentService extends IContentService.Stub { } } - private String getUriSegment(Uri uri, int index) { + public static String getUriSegment(Uri uri, int index) { if (uri != null) { if (index == 0) { return uri.getAuthority(); @@ -1545,7 +1562,7 @@ public final class ContentService extends IContentService.Stub { } } - private int countUriSegments(Uri uri) { + public static int countUriSegments(Uri uri) { if (uri == null) { return 0; } @@ -1669,14 +1686,21 @@ public final class ContentService extends IContentService.Stub { } } + @VisibleForTesting + public void collectObserversLocked(Uri uri, int index, + IContentObserver observer, boolean observerWantsSelfNotifications, int flags, + int targetUserHandle, ObserverCollector collector) { + collectObserversLocked(uri, countUriSegments(uri), index, observer, + observerWantsSelfNotifications, flags, targetUserHandle, collector); + } + /** * targetUserHandle is either a hard user handle or is USER_ALL */ - public void collectObserversLocked(Uri uri, int index, IContentObserver observer, - boolean observerWantsSelfNotifications, int flags, - int targetUserHandle, ObserverCollector collector) { + public void collectObserversLocked(Uri uri, int segmentCount, int index, + IContentObserver observer, boolean observerWantsSelfNotifications, int flags, + int targetUserHandle, ObserverCollector collector) { String segment = null; - int segmentCount = countUriSegments(uri); if (index >= segmentCount) { // This is the leaf node, notify all observers if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName); @@ -1696,7 +1720,7 @@ public final class ContentService extends IContentService.Stub { ObserverNode node = mChildren.get(i); if (segment == null || node.mName.equals(segment)) { // We found the child, - node.collectObserversLocked(uri, index + 1, observer, + node.collectObserversLocked(uri, segmentCount, index + 1, observer, observerWantsSelfNotifications, flags, targetUserHandle, collector); if (segment != null) { break; diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 6178e6c1e094..f4d7f9ac5a5e 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -200,7 +200,7 @@ class AutomaticBrightnessController { // Context-sensitive brightness configurations require keeping track of the foreground app's // package name and category, which is done by registering a TaskStackListener to call back to // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's - // package namd and PackageManager to get its category (so might as well cache them). + // package name and PackageManager to get its category (so might as well cache them). private String mForegroundAppPackageName; private String mPendingForegroundAppPackageName; private @ApplicationInfo.Category int mForegroundAppCategory; @@ -210,6 +210,7 @@ class AutomaticBrightnessController { private PackageManager mPackageManager; private Context mContext; + private DisplayDeviceConfig mDisplayDeviceConfig; private final Injector mInjector; AutomaticBrightnessController(Callbacks callbacks, Looper looper, @@ -218,12 +219,14 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context) { + HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig + displayDeviceConfig) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, - ambientBrightnessThresholds, screenBrightnessThresholds, context); + ambientBrightnessThresholds, screenBrightnessThresholds, context, + displayDeviceConfig); } @VisibleForTesting @@ -233,7 +236,8 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context) { + HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig + displayDeviceConfig) { mInjector = injector; mContext = context; mCallbacks = callbacks; @@ -260,7 +264,7 @@ class AutomaticBrightnessController { mScreenBrightnessThresholds = screenBrightnessThresholds; mShortTermModelValid = true; mShortTermModelAnchor = -1; - + mDisplayDeviceConfig = displayDeviceConfig; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon); diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 696daca79092..3516981c92a6 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -37,6 +37,7 @@ abstract class DisplayDevice { private final DisplayAdapter mDisplayAdapter; private final IBinder mDisplayToken; private final String mUniqueId; + private DisplayDeviceConfig mDisplayDeviceConfig; // The display device does not manage these properties itself, they are set by // the display manager service. The display device shouldn't really be looking at these. @@ -68,6 +69,16 @@ abstract class DisplayDevice { return mDisplayAdapter; } + /* + * Gets the DisplayDeviceConfig for this DisplayDevice. + * Returns null for this device but is overridden in LocalDisplayDevice. + * + * @return The DisplayDeviceConfig. + */ + public DisplayDeviceConfig getDisplayDeviceConfig() { + return mDisplayDeviceConfig; + } + /** * Gets the Surface Flinger display token for this display. * diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3afbf661f97e..a87fb8b5c301 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1362,7 +1362,8 @@ public final class DisplayManagerService extends SystemService { return null; } - private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) { + private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId, + boolean captureSecureLayer) { synchronized (mSyncRoot) { final IBinder token = getDisplayToken(displayId); if (token == null) { @@ -1374,9 +1375,15 @@ public final class DisplayManagerService extends SystemService { } final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), - displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), - false /* useIdentityTransform */, 0 /* rotation */); + if (captureSecureLayer) { + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), + false /* useIdentityTransform */, 0 /* rotation */); + } else { + return SurfaceControl.screenshotToBuffer(token, new Rect(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), + false /* useIdentityTransform */, 0 /* rotation */); + } } } @@ -2469,7 +2476,8 @@ public final class DisplayManagerService extends SystemService { } }; mDisplayPowerController = new DisplayPowerController( - mContext, callbacks, handler, sensorManager, blanker); + mContext, callbacks, handler, sensorManager, blanker, + mDisplayDevices.get(Display.DEFAULT_DISPLAY)); mSensorManager = sensorManager; } @@ -2494,7 +2502,12 @@ public final class DisplayManagerService extends SystemService { @Override public SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId) { - return screenshotInternal(displayId); + return screenshotInternal(displayId, true); + } + + @Override + public SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(int displayId) { + return screenshotInternal(displayId, false); } @Override @@ -2503,6 +2516,17 @@ public final class DisplayManagerService extends SystemService { } @Override + public Point getDisplayPosition(int displayId) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + return display.getDisplayPosition(); + } + return null; + } + } + + @Override public void registerDisplayTransactionListener(DisplayTransactionListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 197842ed1c2d..f82ec82ce79b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -161,6 +161,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The display blanker. private final DisplayBlanker mBlanker; + // The display device. + private final DisplayDevice mDisplayDevice; + // Tracker for brightness changes. private final BrightnessTracker mBrightnessTracker; @@ -348,11 +351,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Nullable private BrightnessConfiguration mBrightnessConfiguration; - // The last brightness that was set by the user and not temporary. Set to -1 when a brightness - // has yet to be recorded. + // The last brightness that was set by the user and not temporary. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded. private float mLastUserSetScreenBrightness; - // The screen brightenss setting has changed but not taken effect yet. If this is different + // The screen brightness setting has changed but not taken effect yet. If this is different // from the current screen brightness setting then this is coming from something other than us // and should be considered a user interaction. private float mPendingScreenBrightnessSetting; @@ -377,8 +380,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float mPendingAutoBrightnessAdjustment; // The temporary auto brightness adjustment. Typically set when a user is interacting with the - // adjustment slider but hasn't settled on a choice yet. Set to Float.NaN when there's no - // temporary adjustment set. + // adjustment slider but hasn't settled on a choice yet. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set. private float mTemporaryAutoBrightnessAdjustment; // Animators. @@ -386,27 +389,29 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private ObjectAnimator mColorFadeOffAnimator; private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; - private BrightnessSynchronizer mBrightnessSynchronizer; /** * Creates the display power controller. */ public DisplayPowerController(Context context, DisplayPowerCallbacks callbacks, Handler handler, - SensorManager sensorManager, DisplayBlanker blanker) { + SensorManager sensorManager, DisplayBlanker blanker, DisplayDevice displayDevice) { mHandler = new DisplayControllerHandler(handler.getLooper()); mBrightnessTracker = new BrightnessTracker(context, null); mSettingsObserver = new SettingsObserver(mHandler); mCallbacks = callbacks; - mBrightnessSynchronizer = new BrightnessSynchronizer(context); mBatteryStats = BatteryStatsService.getService(); mSensorManager = sensorManager; mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); mBlanker = blanker; mContext = context; + mDisplayDevice = displayDevice; PowerManager pm = context.getSystemService(PowerManager.class); + DisplayDeviceConfig displayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig(); + final Resources resources = context.getResources(); + final float screenBrightnessSettingMinimumFloat = clampAbsoluteBrightness( pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM)); @@ -498,7 +503,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, - screenBrightnessThresholds, context); + screenBrightnessThresholds, context, displayDeviceConfig); } else { mUseSoftwareAutoBrightnessConfig = false; } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 8eb771046e6d..6132467103a9 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -203,6 +203,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { private Spline mNitsToHalBrightness; private boolean mHalBrightnessSupport; + private DisplayDeviceConfig mDisplayDeviceConfig; + LocalDisplayDevice(IBinder displayToken, long physicalDisplayId, SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs, int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs, @@ -224,7 +226,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken); mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken); mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken); - + mDisplayDeviceConfig = null; // Defer configuration file loading BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( LocalDisplayDevice::loadDisplayConfigurationBrightnessMapping, this)); @@ -373,17 +375,23 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + @Override + public DisplayDeviceConfig getDisplayDeviceConfig() { + return mDisplayDeviceConfig; + } + private void loadDisplayConfigurationBrightnessMapping() { Spline nitsToHal = null; Spline sysToNits = null; // Load the mapping from nits to HAL brightness range (display-device-config.xml) DisplayDeviceConfig config = DisplayDeviceConfig.create(mPhysicalDisplayId); + mDisplayDeviceConfig = config; if (config == null) { return; } - final float[] halNits = config.getNits(); - final float[] halBrightness = config.getBrightness(); + final float[] halNits = mDisplayDeviceConfig.getNits(); + final float[] halBrightness = mDisplayDeviceConfig.getBrightness(); if (halNits == null || halBrightness == null) { return; } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 3a5aa93d205d..0261f388f7cb 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManagerInternal; import android.view.Display; @@ -98,6 +99,11 @@ final class LogicalDisplay { private int mDisplayOffsetY; /** + * The position of the display projection sent to SurfaceFlinger + */ + private final Point mDisplayPosition = new Point(); + + /** * {@code true} if display scaling is disabled, or {@code false} if the default scaling mode * is used. * @see #isDisplayScalingDisabled() @@ -335,6 +341,16 @@ final class LogicalDisplay { } /** + * Returns the position of the display's projection. + * + * @return The x, y coordinates of the display. The return object must be treated as immutable. + */ + Point getDisplayPosition() { + // Allocate a new object to avoid a data race. + return new Point(mDisplayPosition); + } + + /** * Applies the layer stack and transformation to the given display device * so that it shows the contents of this logical display. * @@ -445,6 +461,8 @@ final class LogicalDisplay { } else { // Surface.ROTATION_270 mTempDisplayRect.offset(-mDisplayOffsetY, mDisplayOffsetX); } + + mDisplayPosition.set(mTempDisplayRect.left, mTempDisplayRect.top); device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect); } diff --git a/services/core/java/com/android/server/display/color/AppSaturationController.java b/services/core/java/com/android/server/display/color/AppSaturationController.java index e42be02d4a9e..6a685bf187ad 100644 --- a/services/core/java/com/android/server/display/color/AppSaturationController.java +++ b/services/core/java/com/android/server/display/color/AppSaturationController.java @@ -17,6 +17,7 @@ package com.android.server.display.color; import android.annotation.UserIdInt; +import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -61,11 +62,12 @@ class AppSaturationController { * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given * package name and userId. */ - public boolean setSaturationLevel(String packageName, @UserIdInt int userId, + public boolean setSaturationLevel(String callingPackageName, String affectedPackageName, + @UserIdInt int userId, int saturationLevel) { synchronized (mLock) { - return getSaturationControllerLocked(packageName, userId) - .setSaturationLevel(saturationLevel); + return getSaturationControllerLocked(affectedPackageName, userId) + .setSaturationLevel(callingPackageName, saturationLevel); } } @@ -148,13 +150,19 @@ class AppSaturationController { private static class SaturationController { + private static final int FULL_SATURATION = 100; + private final List<WeakReference<ColorTransformController>> mControllerRefs = new ArrayList<>(); - private int mSaturationLevel = 100; + private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>(); private float[] mTransformMatrix = new float[9]; - private boolean setSaturationLevel(int saturationLevel) { - mSaturationLevel = saturationLevel; + private boolean setSaturationLevel(String callingPackageName, int saturationLevel) { + if (saturationLevel == FULL_SATURATION) { + mSaturationLevels.remove(callingPackageName); + } else { + mSaturationLevels.put(callingPackageName, saturationLevel); + } if (!mControllerRefs.isEmpty()) { return updateState(); } @@ -163,17 +171,27 @@ class AppSaturationController { private boolean addColorTransformController( WeakReference<ColorTransformController> controller) { + clearExpiredReferences(); mControllerRefs.add(controller); - if (mSaturationLevel != 100) { + if (!mSaturationLevels.isEmpty()) { return updateState(); - } else { - clearExpiredReferences(); } return false; } + private int calculateSaturationLevel() { + int saturationLevel = FULL_SATURATION; + for (int i = 0; i < mSaturationLevels.size(); i++) { + final int level = mSaturationLevels.valueAt(i); + if (level < saturationLevel) { + saturationLevel = level; + } + } + return saturationLevel; + } + private boolean updateState() { - computeGrayscaleTransformMatrix(mSaturationLevel / 100f, mTransformMatrix); + computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix); boolean updated = false; final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs @@ -190,7 +208,6 @@ class AppSaturationController { } } return updated; - } private void clearExpiredReferences() { @@ -206,7 +223,7 @@ class AppSaturationController { } private void dump(PrintWriter pw) { - pw.println(" mSaturationLevel: " + mSaturationLevel); + pw.println(" mSaturationLevels: " + mSaturationLevels); pw.println(" mControllerRefs count: " + mControllerRefs.size()); } } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 2dc2cf0d8e90..95a98f1e9494 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -44,6 +44,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.display.ColorDisplayManager; @@ -73,6 +74,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; @@ -817,9 +819,11 @@ public final class ColorDisplayService extends SystemService { return LocalDateTime.MIN; } - private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) { + private boolean setAppSaturationLevelInternal(String callingPackageName, + String affectedPackageName, int saturationLevel) { return mAppSaturationController - .setSaturationLevel(packageName, mCurrentUser, saturationLevel); + .setSaturationLevel(callingPackageName, affectedPackageName, mCurrentUser, + saturationLevel); } private void setColorModeInternal(@ColorMode int colorMode) { @@ -1533,9 +1537,11 @@ public final class ColorDisplayService extends SystemService { getContext().enforceCallingPermission( Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, "Permission required to set display saturation level"); + final String callingPackageName = LocalServices.getService(PackageManagerInternal.class) + .getNameForUid(Binder.getCallingUid()); final long token = Binder.clearCallingIdentity(); try { - return setAppSaturationLevelInternal(packageName, level); + return setAppSaturationLevelInternal(callingPackageName, packageName, level); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java index 9c279166ac8a..cb64c50bf11d 100644 --- a/services/core/java/com/android/server/location/AppOpsHelper.java +++ b/services/core/java/com/android/server/location/AppOpsHelper.java @@ -191,7 +191,7 @@ public class AppOpsHelper { callerIdentity.uid, callerIdentity.packageName, callerIdentity.featureId, - null) == AppOpsManager.MODE_ALLOWED; + callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; } finally { Binder.restoreCallingIdentity(identity); } @@ -210,7 +210,7 @@ public class AppOpsHelper { callerIdentity.packageName, false, callerIdentity.featureId, - null) == AppOpsManager.MODE_ALLOWED; + callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; } finally { Binder.restoreCallingIdentity(identity); } @@ -245,7 +245,7 @@ public class AppOpsHelper { callerIdentity.uid, callerIdentity.packageName, callerIdentity.featureId, - null) == AppOpsManager.MODE_ALLOWED; + callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java index b84fd13415fc..8d508bb6256d 100644 --- a/services/core/java/com/android/server/location/CallerIdentity.java +++ b/services/core/java/com/android/server/location/CallerIdentity.java @@ -83,12 +83,22 @@ public final class CallerIdentity { */ public static CallerIdentity fromBinder(Context context, String packageName, @Nullable String featureId) { + return fromBinder(context, packageName, featureId, null); + } + + /** + * Creates a CallerIdentity from the current binder identity, using the given package, feature + * id, and listener id. The package will be checked to enforce it belongs to the calling uid, + * and a security exception will be thrown if it is invalid. + */ + public static CallerIdentity fromBinder(Context context, String packageName, + @Nullable String featureId, @Nullable String listenerId) { int uid = Binder.getCallingUid(); if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) { throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid); } - return fromBinderUnsafe(context, packageName, featureId); + return fromBinderUnsafe(context, packageName, featureId, listenerId); } /** @@ -99,8 +109,19 @@ public final class CallerIdentity { */ public static CallerIdentity fromBinderUnsafe(Context context, String packageName, @Nullable String featureId) { + return fromBinderUnsafe(context, packageName, featureId, null); + } + + /** + * Creates a CallerIdentity from the current binder identity, using the given package, feature + * id, and listener id. The package will not be checked to enforce that it belongs to the + * calling uid - this method should only be used if the package will be validated by some other + * means, such as an appops call. + */ + public static CallerIdentity fromBinderUnsafe(Context context, String packageName, + @Nullable String featureId, @Nullable String listenerId) { return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), - UserHandle.getCallingUserId(), packageName, featureId, + UserHandle.getCallingUserId(), packageName, featureId, listenerId, getBinderPermissionLevel(context)); } @@ -157,6 +178,9 @@ public final class CallerIdentity { /** The calling feature id. */ public final @Nullable String featureId; + /** The calling listener id. */ + public final @Nullable String listenerId; + /** * The calling location permission level. This field should only be used for validating * permissions for API access. It should not be used for validating permissions for location @@ -167,11 +191,18 @@ public final class CallerIdentity { @VisibleForTesting public CallerIdentity(int uid, int pid, int userId, String packageName, @Nullable String featureId, @PermissionLevel int permissionLevel) { + this(uid, pid, userId, packageName, featureId, null, permissionLevel); + } + + private CallerIdentity(int uid, int pid, int userId, String packageName, + @Nullable String featureId, @Nullable String listenerId, + @PermissionLevel int permissionLevel) { this.uid = uid; this.pid = pid; this.userId = userId; this.packageName = Objects.requireNonNull(packageName); this.featureId = featureId; + this.listenerId = listenerId; this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE, PERMISSION_FINE, "permissionLevel"); } @@ -216,7 +247,8 @@ public final class CallerIdentity { return uid == that.uid && pid == that.pid && packageName.equals(that.packageName) - && Objects.equals(featureId, that.featureId); + && Objects.equals(featureId, that.featureId) + && Objects.equals(listenerId, that.listenerId); } @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index ad3c8a61182f..d8acf0e331af 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -809,11 +809,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements locationRequest.setProvider(provider); // Ignore location settings if in emergency mode. This is only allowed for - // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1. + // isUserEmergency request (introduced in HAL v2.0), or HAL v1.1. if (mNIHandler.getInEmergency()) { GnssConfiguration.HalInterfaceVersion halVersion = mGnssConfiguration.getHalInterfaceVersion(); - if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) { + if (isUserEmergency || halVersion.mMajor < 2) { locationRequest.setLocationSettingsIgnored(true); durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER; } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 2aa53cc3882e..a5de90c93aab 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -169,6 +169,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } public void rebindIfDisconnected() { + //TODO: When we are connecting to the service, calling this will unbind and bind again. + // We'd better not unbind if we are connecting. if (mActiveConnection == null && shouldBind()) { unbind(); bind(); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index fe118e51cdce..b688e0922d49 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -83,7 +83,7 @@ final class MediaRoute2ProviderWatcher { // Scan packages. // Also has the side-effect of restarting providers if needed. - mHandler.post(mScanPackagesRunnable); + postScanPackagesIfNeeded(); } } @@ -92,7 +92,7 @@ final class MediaRoute2ProviderWatcher { mRunning = false; mContext.unregisterReceiver(mScanPackagesReceiver); - mHandler.removeCallbacks(mScanPackagesRunnable); + mHandler.removeCallbacks(this::scanPackages); // Stop all providers. for (int i = mProxies.size() - 1; i >= 0; i--) { @@ -154,20 +154,19 @@ final class MediaRoute2ProviderWatcher { return -1; } + private void postScanPackagesIfNeeded() { + if (!mHandler.hasCallbacks(this::scanPackages)) { + mHandler.post(this::scanPackages); + } + } + private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Received package manager broadcast: " + intent); } - scanPackages(); - } - }; - - private final Runnable mScanPackagesRunnable = new Runnable() { - @Override - public void run() { - scanPackages(); + postScanPackagesIfNeeded(); } }; diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 52e9d7c67605..c3413e8d2934 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -98,18 +98,26 @@ class MediaRouter2ServiceImpl { public List<MediaRoute2Info> getSystemRoutes() { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED; final long token = Binder.clearCallingIdentity(); try { Collection<MediaRoute2Info> systemRoutes; synchronized (mLock) { UserRecord userRecord = getOrCreateUserRecordLocked(userId); - MediaRoute2ProviderInfo providerInfo = - userRecord.mHandler.mSystemProvider.getProviderInfo(); - if (providerInfo != null) { - systemRoutes = providerInfo.getRoutes(); + if (hasModifyAudioRoutingPermission) { + MediaRoute2ProviderInfo providerInfo = + userRecord.mHandler.mSystemProvider.getProviderInfo(); + if (providerInfo != null) { + systemRoutes = providerInfo.getRoutes(); + } else { + systemRoutes = Collections.emptyList(); + } } else { - systemRoutes = Collections.emptyList(); + systemRoutes = new ArrayList<>(); + systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute()); } } return new ArrayList<>(systemRoutes); @@ -122,18 +130,25 @@ class MediaRouter2ServiceImpl { public RoutingSessionInfo getSystemSessionInfo() { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED; final long token = Binder.clearCallingIdentity(); try { RoutingSessionInfo systemSessionInfo = null; synchronized (mLock) { UserRecord userRecord = getOrCreateUserRecordLocked(userId); - List<RoutingSessionInfo> sessionInfos = - userRecord.mHandler.mSystemProvider.getSessionInfos(); - if (sessionInfos != null && !sessionInfos.isEmpty()) { - systemSessionInfo = sessionInfos.get(0); + List<RoutingSessionInfo> sessionInfos; + if (hasModifyAudioRoutingPermission) { + sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos(); + if (sessionInfos != null && !sessionInfos.isEmpty()) { + systemSessionInfo = sessionInfos.get(0); + } else { + Slog.w(TAG, "System provider does not have any session info."); + } } else { - Slog.w(TAG, "System provider does not have any session info."); + systemSessionInfo = userRecord.mHandler.mSystemProvider.getDefaultSessionInfo(); } } return systemSessionInfo; @@ -654,10 +669,20 @@ class MediaRouter2ServiceImpl { return; } - routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::transferToRouteOnHandler, - routerRecord.mUserRecord.mHandler, - DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route)); + String defaultRouteId = + routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId(); + if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission + && !TextUtils.equals(route.getId(), defaultRouteId)) { + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::notifySessionCreationFailedToRouter, + routerRecord.mUserRecord.mHandler, + routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID))); + } else { + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::transferToRouteOnHandler, + routerRecord.mUserRecord.mHandler, + DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route)); + } } private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router, @@ -808,7 +833,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -829,7 +854,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -850,7 +875,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -1185,18 +1210,42 @@ class MediaRouter2ServiceImpl { } } - List<IMediaRouter2> routers = getRouters(); + List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true); + List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false); List<IMediaRouter2Manager> managers = getManagers(); + List<MediaRoute2Info> defaultRoute = new ArrayList<>(); + defaultRoute.add(mSystemProvider.getDefaultRoute()); + if (addedRoutes.size() > 0) { - notifyRoutesAddedToRouters(routers, addedRoutes); + notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes); + if (!provider.mIsSystemRouteProvider) { + notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission, + addedRoutes); + } else if (prevInfo == null) { + notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission, + defaultRoute); + } // 'else' is handled as changed routes notifyRoutesAddedToManagers(managers, addedRoutes); } if (removedRoutes.size() > 0) { - notifyRoutesRemovedToRouters(routers, removedRoutes); + notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission, + removedRoutes); + if (!provider.mIsSystemRouteProvider) { + notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission, + removedRoutes); + } notifyRoutesRemovedToManagers(managers, removedRoutes); } if (changedRoutes.size() > 0) { - notifyRoutesChangedToRouters(routers, changedRoutes); + notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission, + changedRoutes); + if (!provider.mIsSystemRouteProvider) { + notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission, + changedRoutes); + } else if (prevInfo != null) { + notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission, + defaultRoute); + } // 'else' is handled as added routes notifyRoutesChangedToManagers(managers, changedRoutes); } } @@ -1223,6 +1272,15 @@ class MediaRouter2ServiceImpl { toOriginalRequestId(uniqueRequestId)); return; } + if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission + && !TextUtils.equals(route.getId(), + mSystemProvider.getDefaultRoute().getId())) { + Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to" + + route); + notifySessionCreationFailedToRouter(routerRecord, + toOriginalRequestId(uniqueRequestId)); + return; + } SessionCreationRequest request = new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord); @@ -1232,7 +1290,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId(), sessionHints); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route, @@ -1250,7 +1308,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { @@ -1270,7 +1328,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void transferToRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { @@ -1289,6 +1347,8 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } + // routerRecord is null if and only if the session is created without the request, which + // includes the system's session and RCN cases. private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description) { @@ -1305,12 +1365,6 @@ class MediaRouter2ServiceImpl { return true; } - //TODO(b/152950479): Handle RCN case. - if (routerRecord == null) { - Slog.w(TAG, "Ignoring " + description + " route from unknown router."); - return false; - } - RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId); if (matchingRecord != routerRecord) { Slog.w(TAG, "Ignoring " + description + " route from non-matching router. " @@ -1448,7 +1502,9 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - notifySessionInfoChangedToRouters(getRouters(), sessionInfo); + notifySessionInfoChangedToRouters(getRouters(true), sessionInfo); + notifySessionInfoChangedToRouters(getRouters(false), + mSystemProvider.getDefaultSessionInfo()); return; } @@ -1569,7 +1625,7 @@ class MediaRouter2ServiceImpl { } } - private List<IMediaRouter2> getRouters() { + private List<IMediaRouter2> getAllRouters() { final List<IMediaRouter2> routers = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { @@ -1583,6 +1639,23 @@ class MediaRouter2ServiceImpl { return routers; } + private List<IMediaRouter2> getRouters(boolean hasModifyAudioRoutingPermission) { + final List<IMediaRouter2> routers = new ArrayList<>(); + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return routers; + } + synchronized (service.mLock) { + for (RouterRecord routerRecord : mUserRecord.mRouterRecords) { + if (hasModifyAudioRoutingPermission + == routerRecord.mHasModifyAudioRoutingPermission) { + routers.add(routerRecord.mRouter); + } + } + } + return routers; + } + private List<IMediaRouter2Manager> getManagers() { final List<IMediaRouter2Manager> managers = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 5b16d686e04c..6e2feeb15e21 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -72,6 +72,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // This should be the currently selected route. MediaRoute2Info mDefaultRoute; MediaRoute2Info mDeviceRoute; + RoutingSessionInfo mDefaultSessionInfo; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @@ -114,6 +115,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } }); updateSessionInfosIfNeeded(); + mContext.registerReceiver(new VolumeChangeReceiver(), new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); @@ -156,6 +158,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @Override public void transferToRoute(long requestId, String sessionId, String routeId) { + if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) { + // The currently selected route is the default route. + return; + } if (mBtRouteProvider != null) { if (TextUtils.equals(routeId, mDeviceRoute.getId())) { mBtRouteProvider.transferTo(null); @@ -182,6 +188,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return mDefaultRoute; } + public RoutingSessionInfo getDefaultSessionInfo() { + return mDefaultSessionInfo; + } + private void updateDeviceRoute(AudioRoutesInfo newRoutes) { int name = R.string.default_audio_route_name; if (newRoutes != null) { @@ -229,8 +239,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { */ boolean updateSessionInfosIfNeeded() { synchronized (mLock) { - // Prevent to execute this method before mBtRouteProvider is created. - if (mBtRouteProvider == null) return false; RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get( 0); @@ -238,14 +246,19 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SYSTEM_SESSION_ID, "" /* clientPackageName */) .setSystemSession(true); - MediaRoute2Info selectedRoute = mBtRouteProvider.getSelectedRoute(); - if (selectedRoute == null) { - selectedRoute = mDeviceRoute; - } else { - builder.addTransferableRoute(mDeviceRoute.getId()); + MediaRoute2Info selectedRoute = mDeviceRoute; + if (mBtRouteProvider != null) { + MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute(); + if (selectedBtRoute != null) { + selectedRoute = selectedBtRoute; + builder.addTransferableRoute(mDeviceRoute.getId()); + } } mSelectedRouteId = selectedRoute.getId(); - mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute).build(); + mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute) + .setSystemRoute(true) + .setProviderId(mUniqueId) + .build(); builder.addSelectedRoute(mSelectedRouteId); for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) { @@ -258,6 +271,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } else { mSessionInfos.clear(); mSessionInfos.add(newSessionInfo); + mDefaultSessionInfo = new RoutingSessionInfo.Builder( + SYSTEM_SESSION_ID, "" /* clientPackageName */) + .setProviderId(mUniqueId) + .setSystemSession(true) + .addSelectedRoute(DEFAULT_ROUTE_ID) + .build(); return true; } } @@ -302,6 +321,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } else if (mBtRouteProvider != null) { mBtRouteProvider.setSelectedRouteVolume(newVolume); } + mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute) + .setVolume(newVolume) + .build(); publishProviderState(); } } diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java new file mode 100644 index 000000000000..0bdf3f22ee9a --- /dev/null +++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.net.NetworkTemplate.getCollapsedRatType; + +import android.annotation.NonNull; +import android.content.Context; +import android.telephony.Annotation; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; + +/** + * Helper class that watches for events that are triggered per subscription. + */ +// TODO (b/152176562): Write tests to verify subscription changes generate corresponding +// register/unregister calls. +public class NetworkStatsSubscriptionsMonitor extends + SubscriptionManager.OnSubscriptionsChangedListener { + + /** + * Interface that this monitor uses to delegate event handling to NetworkStatsService. + */ + public interface Delegate { + /** + * Notify that the collapsed RAT type has been changed for any subscription. The method + * will also be triggered for any existing sub when start and stop monitoring. + * + * @param subscriberId IMSI of the subscription. + * @param collapsedRatType collapsed RAT type. + * @see android.net.NetworkTemplate#getCollapsedRatType(int). + */ + void onCollapsedRatTypeChanged(@NonNull String subscriberId, + @Annotation.NetworkType int collapsedRatType); + } + private final Delegate mDelegate; + + /** + * Receivers that watches for {@link ServiceState} changes for each subscription, to + * monitor the transitioning between Radio Access Technology(RAT) types for each sub. + */ + @NonNull + private final CopyOnWriteArrayList<RatTypeListener> mRatListeners = + new CopyOnWriteArrayList<>(); + + @NonNull + private final SubscriptionManager mSubscriptionManager; + @NonNull + private final TelephonyManager mTeleManager; + + @NonNull + private final Executor mExecutor; + + NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor, + @NonNull Delegate delegate) { + super(); + mSubscriptionManager = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mExecutor = executor; + mDelegate = delegate; + } + + @Override + public void onSubscriptionsChanged() { + // Collect active subId list, hidden subId such as opportunistic subscriptions are + // also needed to track CBRS. + final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager); + + for (final int subId : newSubs) { + final RatTypeListener match = CollectionUtils.find(mRatListeners, + it -> it.mSubId == subId); + if (match != null) continue; + + // Create listener for every newly added sub. Also store subscriberId into it to + // prevent binder call to telephony when querying RAT. + final String subscriberId = mTeleManager.getSubscriberId(subId); + if (TextUtils.isEmpty(subscriberId)) { + Log.wtf(NetworkStatsService.TAG, + "Empty subscriberId for newly added sub: " + subId); + } + final RatTypeListener listener = + new RatTypeListener(mExecutor, this, subId, subscriberId); + mRatListeners.add(listener); + + // Register listener to the telephony manager that associated with specific sub. + mTeleManager.createForSubscriptionId(subId) + .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + + for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { + // If the new list contains the subId of the listener, keeps it. + final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId); + if (match != null) continue; + + handleRemoveRatTypeListener(listener); + } + } + + @NonNull + private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) { + final ArrayList<Integer> ret = new ArrayList<>(); + final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList(); + for (int id : ids) ret.add(id); + return ret; + } + + /** + * Get a collapsed RatType for the given subscriberId. + * + * @param subscriberId the target subscriberId + * @return collapsed RatType for the given subscriberId + */ + public int getRatTypeForSubscriberId(@NonNull String subscriberId) { + final RatTypeListener match = CollectionUtils.find(mRatListeners, + it -> TextUtils.equals(subscriberId, it.mSubscriberId)); + return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + + /** + * Start monitoring events that triggered per subscription. + */ + public void start() { + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this); + } + + /** + * Unregister subscription changes and all listeners for each subscription. + */ + public void stop() { + mSubscriptionManager.removeOnSubscriptionsChangedListener(this); + + for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { + handleRemoveRatTypeListener(listener); + } + } + + private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) { + mTeleManager.createForSubscriptionId(listener.mSubId) + .listen(listener, PhoneStateListener.LISTEN_NONE); + mRatListeners.remove(listener); + + // Removal of subscriptions doesn't generate RAT changed event, fire it for every + // RatTypeListener. + mDelegate.onCollapsedRatTypeChanged( + listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + static class RatTypeListener extends PhoneStateListener { + // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}. + @NonNull + private final int mSubId; + + // IMSI to identifying the corresponding network from {@link NetworkState}. + // See {@link TelephonyManager#getSubscriberId}. + @NonNull + private final String mSubscriberId; + + private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + @NonNull + private final NetworkStatsSubscriptionsMonitor mMonitor; + + RatTypeListener(@NonNull Executor executor, + @NonNull NetworkStatsSubscriptionsMonitor monitor, int subId, + @NonNull String subscriberId) { + super(executor); + mSubId = subId; + mSubscriberId = subscriberId; + mMonitor = monitor; + } + + @Override + public void onServiceStateChanged(@NonNull ServiceState ss) { + final int networkType = ss.getDataNetworkType(); + final int collapsedRatType = getCollapsedRatType(networkType); + if (collapsedRatType == mLastCollapsedRatType) return; + + if (NetworkStatsService.LOGD) { + Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): " + + mLastCollapsedRatType + " -> " + collapsedRatType); + } + mLastCollapsedRatType = collapsedRatType; + mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType); + } + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ed3b9f1fc265..f9fc82bf05b1 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -214,7 +214,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -2745,15 +2744,15 @@ public class NotificationManagerService extends SystemService { return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId; } - private ToastRecord getToastRecord(int pid, String packageName, IBinder token, + private ToastRecord getToastRecord(int uid, int pid, String packageName, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, Binder windowToken, int displayId, @Nullable ITransientNotificationCallback textCallback) { if (callback == null) { - return new TextToastRecord(this, mStatusBar, pid, packageName, token, text, duration, - windowToken, displayId, textCallback); + return new TextToastRecord(this, mStatusBar, uid, pid, packageName, token, text, + duration, windowToken, displayId, textCallback); } else { - return new CustomToastRecord(this, pid, packageName, token, callback, duration, + return new CustomToastRecord(this, uid, pid, packageName, token, callback, duration, windowToken, displayId); } } @@ -2878,8 +2877,8 @@ public class NotificationManagerService extends SystemService { Binder windowToken = new Binder(); mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId); - record = getToastRecord(callingPid, pkg, token, text, callback, duration, - windowToken, displayId, textCallback); + record = getToastRecord(callingUid, callingPid, pkg, token, text, callback, + duration, windowToken, displayId, textCallback); mToastQueue.add(record); index = mToastQueue.size() - 1; keepProcessAliveForToastIfNeededLocked(callingPid); @@ -5617,18 +5616,16 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerEnqueuedByApp(pkg); + final StatusBarNotification n = new StatusBarNotification( + pkg, opPkg, id, tag, notificationUid, callingPid, notification, + user, null, System.currentTimeMillis()); + // setup local book-keeping String channelId = notification.getChannelId(); if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { channelId = (new Notification.TvExtender(notification)).getChannelId(); } - String shortcutId = notification.getShortcutId(); - if (FeatureFlagUtils.isEnabled(getContext(), - FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ) - && shortcutId == null - && notification.getNotificationStyle() == Notification.MessagingStyle.class) { - shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID; - } + String shortcutId = n.getShortcutId(getContext()); final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( pkg, notificationUid, channelId, shortcutId, true /* parent ok */, false /* includeDeleted */); @@ -5656,9 +5653,6 @@ public class NotificationManagerService extends SystemService { return; } - final StatusBarNotification n = new StatusBarNotification( - pkg, opPkg, id, tag, notificationUid, callingPid, notification, - user, null, System.currentTimeMillis()); final NotificationRecord r = new NotificationRecord(getContext(), n, channel); r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid)); r.setPostSilently(postSilently); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 54a0f9f46892..192df4139b37 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1383,9 +1383,8 @@ public final class NotificationRecord { || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { return false; } - if (mShortcutInfo == null - && !FeatureFlagUtils.isEnabled( - mContext, FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ)) { + if (mShortcutInfo == null && Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 1) { return false; } if (mIsNotConversationOverride) { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index dbb246e9fbe8..8154988a4917 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -47,7 +47,6 @@ import android.service.notification.RankingHelperProto; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.FeatureFlagUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseBooleanArray; @@ -181,8 +180,8 @@ public class PreferencesHelper implements RankingConfig { updateBadgingEnabled(); updateBubblesEnabled(); syncChannelsBypassingDnd(mContext.getUserId()); - mAllowInvalidShortcuts = FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ); + mAllowInvalidShortcuts = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 0; } public void readXml(XmlPullParser parser, boolean forRestore, int userId) diff --git a/services/core/java/com/android/server/notification/toast/CustomToastRecord.java b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java index aca6f4853597..2b91a00f9da5 100644 --- a/services/core/java/com/android/server/notification/toast/CustomToastRecord.java +++ b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java @@ -23,6 +23,7 @@ import android.app.ITransientNotification; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Slog; import com.android.server.notification.NotificationManagerService; @@ -35,11 +36,10 @@ public class CustomToastRecord extends ToastRecord { public final ITransientNotification callback; - public CustomToastRecord( - NotificationManagerService notificationManager, int pid, String packageName, - IBinder token, ITransientNotification callback, int duration, Binder windowToken, - int displayId) { - super(notificationManager, pid, packageName, token, duration, windowToken, displayId); + public CustomToastRecord(NotificationManagerService notificationManager, int uid, int pid, + String packageName, IBinder token, ITransientNotification callback, int duration, + Binder windowToken, int displayId) { + super(notificationManager, uid, pid, packageName, token, duration, windowToken, displayId); this.callback = checkNotNull(callback); } @@ -74,8 +74,8 @@ public class CustomToastRecord extends ToastRecord { public String toString() { return "CustomToastRecord{" + Integer.toHexString(System.identityHashCode(this)) + + " " + pid + ":" + pkg + "/" + UserHandle.formatUid(uid) + " token=" + token - + " packageName=" + pkg + " callback=" + callback + " duration=" + getDuration() + "}"; diff --git a/services/core/java/com/android/server/notification/toast/TextToastRecord.java b/services/core/java/com/android/server/notification/toast/TextToastRecord.java index 3c231b445f62..544520edc7fc 100644 --- a/services/core/java/com/android/server/notification/toast/TextToastRecord.java +++ b/services/core/java/com/android/server/notification/toast/TextToastRecord.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.app.ITransientNotificationCallback; import android.os.Binder; import android.os.IBinder; +import android.os.UserHandle; import android.util.Slog; import com.android.server.notification.NotificationManagerService; @@ -41,10 +42,10 @@ public class TextToastRecord extends ToastRecord { private final ITransientNotificationCallback mCallback; public TextToastRecord(NotificationManagerService notificationManager, - @Nullable StatusBarManagerInternal statusBarManager, int pid, String packageName, - IBinder token, CharSequence text, int duration, Binder windowToken, int displayId, - @Nullable ITransientNotificationCallback callback) { - super(notificationManager, pid, packageName, token, duration, windowToken, displayId); + @Nullable StatusBarManagerInternal statusBarManager, int uid, int pid, + String packageName, IBinder token, CharSequence text, int duration, Binder windowToken, + int displayId, @Nullable ITransientNotificationCallback callback) { + super(notificationManager, uid, pid, packageName, token, duration, windowToken, displayId); mStatusBar = statusBarManager; mCallback = callback; this.text = checkNotNull(text); @@ -59,7 +60,7 @@ public class TextToastRecord extends ToastRecord { Slog.w(TAG, "StatusBar not available to show text toast for package " + pkg); return false; } - mStatusBar.showToast(pkg, token, text, windowToken, getDuration(), mCallback); + mStatusBar.showToast(uid, pkg, token, text, windowToken, getDuration(), mCallback); return true; } @@ -75,8 +76,8 @@ public class TextToastRecord extends ToastRecord { public String toString() { return "TextToastRecord{" + Integer.toHexString(System.identityHashCode(this)) + + " " + pid + ":" + pkg + "/" + UserHandle.formatUid(uid) + " token=" + token - + " packageName=" + pkg + " text=" + text + " duration=" + getDuration() + "}"; diff --git a/services/core/java/com/android/server/notification/toast/ToastRecord.java b/services/core/java/com/android/server/notification/toast/ToastRecord.java index ef75a6f5dd7b..7915f7013227 100644 --- a/services/core/java/com/android/server/notification/toast/ToastRecord.java +++ b/services/core/java/com/android/server/notification/toast/ToastRecord.java @@ -28,6 +28,7 @@ import java.io.PrintWriter; * Represents a toast, a transient notification. */ public abstract class ToastRecord { + public final int uid; public final int pid; public final String pkg; public final IBinder token; @@ -36,11 +37,10 @@ public abstract class ToastRecord { protected final NotificationManagerService mNotificationManager; private int mDuration; - protected ToastRecord( - NotificationManagerService notificationManager, - int pid, String pkg, IBinder token, int duration, - Binder windowToken, int displayId) { + protected ToastRecord(NotificationManagerService notificationManager, int uid, int pid, + String pkg, IBinder token, int duration, Binder windowToken, int displayId) { this.mNotificationManager = notificationManager; + this.uid = uid; this.pid = pid; this.pkg = pkg; this.token = token; diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index e8e383404bd6..7df8fc7e34ed 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -18,20 +18,19 @@ package com.android.server.om; import static android.content.Context.IDMAP_SERVICE; -import static com.android.server.om.OverlayManagerService.DEBUG; import static com.android.server.om.OverlayManagerService.TAG; import android.os.IBinder; import android.os.IIdmap2; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; +import android.os.SystemClock; +import android.os.SystemService; import android.util.Slog; import com.android.server.FgThread; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -45,13 +44,14 @@ class IdmapDaemon { // The amount of time in milliseconds to wait when attempting to connect to idmap service. private static final int SERVICE_CONNECT_TIMEOUT_MS = 5000; + private static final int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 200; - private static final Object IDMAP_TOKEN = new Object(); private static final String IDMAP_DAEMON = "idmap2d"; private static IdmapDaemon sInstance; private volatile IIdmap2 mService; private final AtomicInteger mOpenedCount = new AtomicInteger(); + private final Object mIdmapToken = new Object(); /** * An {@link AutoCloseable} connection to the idmap service. When the connection is closed or @@ -62,14 +62,14 @@ class IdmapDaemon { private boolean mOpened = true; private Connection() { - synchronized (IDMAP_TOKEN) { + synchronized (mIdmapToken) { mOpenedCount.incrementAndGet(); } } @Override public void close() { - synchronized (IDMAP_TOKEN) { + synchronized (mIdmapToken) { if (!mOpened) { return; } @@ -82,7 +82,7 @@ class IdmapDaemon { } FgThread.getHandler().postDelayed(() -> { - synchronized (IDMAP_TOKEN) { + synchronized (mIdmapToken) { // Only stop the service if the service does not have an open connection. if (mService == null || mOpenedCount.get() != 0) { return; @@ -91,7 +91,7 @@ class IdmapDaemon { stopIdmapService(); mService = null; } - }, IDMAP_TOKEN, SERVICE_TIMEOUT_MS); + }, mIdmapToken, SERVICE_TIMEOUT_MS); } } } @@ -104,14 +104,14 @@ class IdmapDaemon { } String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce, - int userId) throws Exception { - try (Connection connection = connect()) { + int userId) throws TimeoutException, RemoteException { + try (Connection c = connect()) { return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId); } } - boolean removeIdmap(String overlayPath, int userId) throws Exception { - try (Connection connection = connect()) { + boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException { + try (Connection c = connect()) { return mService.removeIdmap(overlayPath, userId); } } @@ -119,76 +119,54 @@ class IdmapDaemon { boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce, int userId) throws Exception { - try (Connection connection = connect()) { + try (Connection c = connect()) { return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId); } } - String getIdmapPath(String overlayPath, int userId) throws Exception { - try (Connection connection = connect()) { + String getIdmapPath(String overlayPath, int userId) throws TimeoutException, RemoteException { + try (Connection c = connect()) { return mService.getIdmapPath(overlayPath, userId); } } - private static void startIdmapService() { - SystemProperties.set("ctl.start", IDMAP_DAEMON); + private IBinder getIdmapService() throws TimeoutException, RemoteException { + SystemService.start(IDMAP_DAEMON); + + final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS; + while (SystemClock.elapsedRealtime() <= endMillis) { + final IBinder binder = ServiceManager.getService(IDMAP_SERVICE); + if (binder != null) { + binder.linkToDeath( + () -> Slog.w(TAG, String.format("service '%s' died", IDMAP_SERVICE)), 0); + return binder; + } + + try { + Thread.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS); + } catch (InterruptedException ignored) { + } + } + + throw new TimeoutException( + String.format("Failed to connect to '%s' in %d milliseconds", IDMAP_SERVICE, + SERVICE_CONNECT_TIMEOUT_MS)); } private static void stopIdmapService() { - SystemProperties.set("ctl.stop", IDMAP_DAEMON); + SystemService.stop(IDMAP_DAEMON); } - private Connection connect() throws Exception { - synchronized (IDMAP_TOKEN) { - FgThread.getHandler().removeCallbacksAndMessages(IDMAP_TOKEN); + private Connection connect() throws TimeoutException, RemoteException { + synchronized (mIdmapToken) { + FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken); if (mService != null) { // Not enough time has passed to stop the idmap service. Reuse the existing // interface. return new Connection(); } - // Start the idmap service if it is not currently running. - startIdmapService(); - - // Block until the service is found. - FutureTask<IBinder> bindIdmap = new FutureTask<>(() -> { - while (true) { - try { - IBinder binder = ServiceManager.getService(IDMAP_SERVICE); - if (binder != null) { - return binder; - } - } catch (Exception e) { - Slog.e(TAG, "service '" + IDMAP_SERVICE + "' not retrieved; " - + e.getMessage()); - } - Thread.sleep(100); - } - }); - - IBinder binder; - try { - FgThread.getHandler().postAtFrontOfQueue(bindIdmap); - binder = bindIdmap.get(SERVICE_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (Exception rethrow) { - Slog.e(TAG, "service '" + IDMAP_SERVICE + "' not found;"); - throw rethrow; - } - - try { - binder.linkToDeath(() -> { - Slog.w(TAG, "service '" + IDMAP_SERVICE + "' died"); - }, 0); - } catch (RemoteException rethrow) { - Slog.e(TAG, "service '" + IDMAP_SERVICE + "' failed to be bound"); - throw rethrow; - } - - mService = IIdmap2.Stub.asInterface(binder); - if (DEBUG) { - Slog.d(TAG, "service '" + IDMAP_SERVICE + "' connected"); - } - + mService = IIdmap2.Stub.asInterface(getIdmapService()); return new Connection(); } } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 43fc7ed0e39d..90c85ada9def 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -63,20 +63,23 @@ class IdmapManager { mIdmapDaemon = IdmapDaemon.getInstance(); } + /** + * Creates the idmap for the target/overlay combination and returns whether the idmap file was + * modified. + */ boolean createIdmap(@NonNull final PackageInfo targetPackage, @NonNull final PackageInfo overlayPackage, int userId) { if (DEBUG) { Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and " + overlayPackage.packageName); } - final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid); final String targetPath = targetPackage.applicationInfo.getBaseCodePath(); final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath(); try { int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId); boolean enforce = enforceOverlayable(overlayPackage); if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) { - return true; + return false; } return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies, enforce, userId) != null; diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index d108e76e37df..05a4a38feef1 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -700,14 +700,15 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName, userId); - // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native layers. + // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native + // layers. + boolean modified = false; if (targetPackage != null && overlayPackage != null && !("android".equals(targetPackageName) && !isPackageConfiguredMutable(overlayPackageName))) { - mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); + modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } - boolean modified = false; if (overlayPackage != null) { modified |= mSettings.setBaseCodePath(overlayPackageName, userId, overlayPackage.applicationInfo.getBaseCodePath()); diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING index 75229a1adccc..6edd76f1810a 100644 --- a/services/core/java/com/android/server/om/TEST_MAPPING +++ b/services/core/java/com/android/server/om/TEST_MAPPING @@ -15,6 +15,9 @@ "name": "OverlayHostTests" }, { + "name": "OverlayRemountedTest" + }, + { "name": "CtsAppSecurityHostTestCases", "options": [ { diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index b9d656181b55..daf4bf271ca2 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -30,7 +30,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.os.Binder; import android.os.Environment; @@ -137,7 +137,8 @@ public abstract class ApexManager { /** * Called by package manager service to scan apex package files when device boots up. * - * @param packageParser The package parser which supports caches. + * @param packageParser The package parser to support apex package parsing and caching parsed + * results. * @param executorService An executor to support parallel package parsing. */ abstract void scanApexPackagesTraced(@NonNull PackageParser2 packageParser, @@ -398,7 +399,9 @@ public abstract class ApexManager { @VisibleForTesting protected IApexService waitForApexService() { try { - return IApexService.Stub.asInterface(Binder.waitForService("apexservice")); + // Since apexd is a trusted platform component, synchronized calls are allowable + return IApexService.Stub.asInterface( + Binder.allowBlocking(Binder.waitForService("apexservice"))); } catch (RemoteException e) { throw new IllegalStateException("Required service apexservice not available"); } @@ -505,7 +508,13 @@ public abstract class ApexManager { } factoryPackagesSet.add(packageInfo.packageName); } - } else if (throwable instanceof PackageParser.PackageParserException) { + } else if (throwable instanceof PackageParserException) { + final PackageParserException e = (PackageParserException) throwable; + // Skip parsing non-coreApp apex file if system is in minimal boot state. + if (e.error == PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED) { + Slog.w(TAG, "Scan apex failed, not a coreApp:" + ai.modulePath); + continue; + } throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable); } else { throw new IllegalStateException("Unexpected exception occurred while parsing " @@ -522,7 +531,8 @@ public abstract class ApexManager { "APEX packages have not been scanned"); boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0; boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0; - for (PackageInfo packageInfo: mAllPackagesCache) { + for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) { + final PackageInfo packageInfo = mAllPackagesCache.get(i); if (!packageInfo.packageName.equals(packageName)) { continue; } @@ -581,7 +591,8 @@ public abstract class ApexManager { if (!isApexSupported()) return false; Preconditions.checkState(mAllPackagesCache != null, "APEX packages have not been scanned"); - for (PackageInfo packageInfo : mAllPackagesCache) { + for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) { + final PackageInfo packageInfo = mAllPackagesCache.get(i); if (packageInfo.packageName.equals(packageName)) { return true; } @@ -839,7 +850,8 @@ public abstract class ApexManager { IndentingPrintWriter ipw) { ipw.println(); ipw.increaseIndent(); - for (PackageInfo pi : packagesCache) { + for (int i = 0, size = packagesCache.size(); i < size; i++) { + final PackageInfo pi = packagesCache.get(i); if (packageName != null && !packageName.equals(pi.packageName)) { continue; } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index ba7583fe7f7a..dab4bfd4df5a 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -46,6 +46,7 @@ import com.android.server.pm.dex.DexoptOptions; import java.io.File; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -106,6 +107,8 @@ public class BackgroundDexOptService extends JobService { private static final long mDowngradeUnusedAppsThresholdInMillis = getDowngradeUnusedAppsThresholdInMillis(); + private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>(); + public static void schedule(Context context) { if (isBackgroundDexoptDisabled()) { return; @@ -244,6 +247,7 @@ public class BackgroundDexOptService extends JobService { } } notifyPinService(updatedPackages); + notifyPackagesUpdated(updatedPackages); // Ran to completion, so we abandon our timeslice and do not reschedule. jobFinished(jobParams, /* reschedule */ false); } @@ -391,6 +395,7 @@ public class BackgroundDexOptService extends JobService { } finally { // Always let the pinner service know about changes. notifyPinService(updatedPackages); + notifyPackagesUpdated(updatedPackages); } } @@ -642,6 +647,32 @@ public class BackgroundDexOptService extends JobService { } } + public static interface PackagesUpdatedListener { + /** Callback when packages have been updated by the bg-dexopt service. */ + public void onPackagesUpdated(ArraySet<String> updatedPackages); + } + + public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) { + synchronized (sPackagesUpdatedListeners) { + sPackagesUpdatedListeners.add(listener); + } + } + + public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) { + synchronized (sPackagesUpdatedListeners) { + sPackagesUpdatedListeners.remove(listener); + } + } + + /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */ + private void notifyPackagesUpdated(ArraySet<String> updatedPackages) { + synchronized (sPackagesUpdatedListeners) { + for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) { + listener.onPackagesUpdated(updatedPackages); + } + } + } + private static long getDowngradeUnusedAppsThresholdInMillis() { final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; String sysPropValue = SystemProperties.get(sysPropKey); diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 40876754eae8..28c8642d3e60 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -294,6 +294,12 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { .getAllCrossProfilePackages().contains(packageName)); } + private boolean isCrossProfilePackageWhitelistedByDefault(String packageName) { + return mInjector.withCleanCallingIdentity(() -> + mInjector.getDevicePolicyManagerInternal() + .getDefaultCrossProfilePackages().contains(packageName)); + } + private List<UserHandle> getTargetUserProfilesUnchecked( String packageName, @UserIdInt int userId) { return mInjector.withCleanCallingIdentity(() -> { @@ -528,6 +534,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { @Override public boolean canConfigureInteractAcrossProfiles(String packageName) { + if (!canUserAttemptToConfigureInteractAcrossProfiles(packageName)) { + return false; + } if (!hasOtherProfileWithPackageInstalled(packageName, mInjector.getCallingUserId())) { return false; } @@ -546,7 +555,35 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { return false; } return hasRequestedAppOpPermission( - AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName); + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName) + && !isPlatformSignedAppWithNonUserConfigurablePermission(packageName, profileIds); + } + + private boolean isPlatformSignedAppWithNonUserConfigurablePermission( + String packageName, int[] profileIds) { + return !isCrossProfilePackageWhitelistedByDefault(packageName) + && isPlatformSignedAppWithAutomaticProfilesPermission(packageName, profileIds); + } + + /** + * Only platform-signed apps can be granted INTERACT_ACROSS_PROFILES automatically without user + * consent. + * + * Returns true if the app is automatically granted the permission in at least one profile. + */ + private boolean isPlatformSignedAppWithAutomaticProfilesPermission( + String packageName, int[] profileIds) { + for (int userId : profileIds) { + final int uid = mInjector.getPackageManagerInternal().getPackageUidInternal( + packageName, /* flags= */ 0, userId); + if (uid == -1) { + continue; + } + if (isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, uid)) { + return true; + } + } + return false; } private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index f37af3aef657..9fb468e8db6e 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -724,6 +724,30 @@ public class Installer extends SystemService { } /** + * Deletes all snapshots of credential encrypted user data, where the snapshot id is not + * included in {@code retainSnapshotIds}. + * + * @param userId id of the user whose user data snapshots to delete. + * @param retainSnapshotIds ids of the snapshots that should not be deleted. + * + * @return {@code true} if the operation was successful, or {@code false} if a remote call + * shouldn't be continued. See {@link #checkBeforeRemote}. + * + * @throws InstallerException if failed to delete user data snapshot. + */ + public boolean destroyCeSnapshotsNotSpecified(@UserIdInt int userId, + int[] retainSnapshotIds) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.destroyCeSnapshotsNotSpecified(null, userId, retainSnapshotIds); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + /** * Migrates obb data from its legacy location {@code /data/media/obb} to * {@code /data/media/0/Android/obb}. This call is idempotent and a fast no-op if data has * already been migrated. diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index e625aeffc0c6..65b7cf3eabd1 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -51,6 +51,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; +import android.os.storage.StorageManager; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -79,7 +80,7 @@ import java.util.Map; * Helper class for running dexopt command on packages. */ public class PackageDexOptimizer { - private static final String TAG = "PackageManager.DexOptimizer"; + private static final String TAG = "PackageDexOptimizer"; static final String OAT_DIR_NAME = "oat"; // TODO b/19550105 Remove error codes and use exceptions public static final int DEX_OPT_SKIPPED = 0; @@ -307,6 +308,55 @@ public class PackageDexOptimizer { } } + /** + * Perform dexopt (if needed) on a system server code path). + */ + public int dexoptSystemServerPath( + String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { + int dexoptFlags = DEXOPT_PUBLIC + | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0) + | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0); + + int result = DEX_OPT_SKIPPED; + for (String isa : dexUseInfo.getLoaderIsas()) { + int dexoptNeeded = getDexoptNeeded( + dexPath, + isa, + options.getCompilerFilter(), + dexUseInfo.getClassLoaderContext(), + /* newProfile= */false, + /* downgrade= */ false); + + if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) { + continue; + } + try { + mInstaller.dexopt( + dexPath, + android.os.Process.SYSTEM_UID, + /* packageName= */ "android", + isa, + dexoptNeeded, + /* oatDir= */ null, + dexoptFlags, + options.getCompilerFilter(), + StorageManager.UUID_PRIVATE_INTERNAL, + dexUseInfo.getClassLoaderContext(), + /* seInfo= */ null, + /* downgrade= */ false , + /* targetSdk= */ 0, + /* profileName */ null, + /* dexMetadataPath */ null, + getReasonName(options.getCompilationReason())); + } catch (InstallerException e) { + Slog.w(TAG, "Failed to dexopt", e); + return DEX_OPT_FAILED; + } + result = DEX_OPT_PERFORMED; + } + return result; + } + private String getAugmentedReasonName(int compilationReason, boolean useDexMetadata) { String annotation = useDexMetadata ? ArtManagerService.DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION : ""; @@ -401,7 +451,8 @@ public class PackageDexOptimizer { return DEX_OPT_FAILED; } String classLoaderContext = null; - if (dexUseInfo.isUnknownClassLoaderContext() || dexUseInfo.isVariableClassLoaderContext()) { + if (dexUseInfo.isUnsupportedClassLoaderContext() + || dexUseInfo.isVariableClassLoaderContext()) { // If we have an unknown (not yet set), or a variable class loader chain. Just extract // the dex file. compilerFilter = "extract"; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 8ff7ea9d61dd..57908f3a4dac 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -82,6 +82,7 @@ import com.android.internal.util.ImageUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; import com.android.server.LocalServices; +import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.permission.PermissionManagerServiceInternal; import libcore.io.IoUtils; @@ -105,6 +106,7 @@ import java.util.List; import java.util.Objects; import java.util.Random; import java.util.function.IntPredicate; +import java.util.function.Supplier; /** The service responsible for installing packages. */ public class PackageInstallerService extends IPackageInstaller.Stub implements @@ -194,7 +196,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; - public PackageInstallerService(Context context, PackageManagerService pm) { + public PackageInstallerService(Context context, PackageManagerService pm, + Supplier<PackageParser2> apexParserSupplier) { mContext = context; mPm = pm; mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class); @@ -213,7 +216,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSessionsDir.mkdirs(); mApexManager = ApexManager.getInstance(); - mStagingManager = new StagingManager(this, context); + mStagingManager = new StagingManager(this, context, apexParserSupplier); } boolean okToSendBroadcasts() { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 1248ec01e020..2221644bff47 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -128,6 +128,7 @@ import com.android.internal.content.PackageHelper; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.pm.Installer.InstallerException; @@ -1801,6 +1802,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private void logDataLoaderInstallationSession(int returnCode, String extraMessage) { + final long currentTimestamp = System.currentTimeMillis(); + FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED, + isIncrementalInstallation(), + mPackageName, + currentTimestamp - createdMillis, + returnCode); + } + /** * Returns true if the session should attempt to inherit any existing native libraries already * extracted at the current install location. This is necessary to prevent double loading of @@ -1830,7 +1840,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throws PackageManagerException { final List<File> addedFiles = getAddedApksLocked(); if (addedFiles.isEmpty()) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + String.format("Session: %d. No packages staged in %s", sessionId, + stageDir.getAbsolutePath())); } if (ArrayUtils.size(addedFiles) > 1) { @@ -1921,7 +1933,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final List<File> addedFiles = getAddedApksLocked(); if (addedFiles.isEmpty() && removeSplitList.size() == 0) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + String.format("Session: %d. No packages staged in %s", sessionId, + stageDir.getAbsolutePath())); } // Verify that all staged packages are internally consistent @@ -2785,6 +2799,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mCallback.onSessionFinished(this, success); + if (isDataLoaderInstallation()) { + logDataLoaderInstallationSession(returnCode, msg); + } } /** {@hide} */ diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f96ab1d9a042..9c41e6d5c6a3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -417,6 +417,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; /** * Keep track of all those APKs everywhere. @@ -3571,7 +3572,11 @@ public class PackageManagerService extends IPackageManager.Stub } } - mInstallerService = new PackageInstallerService(mContext, this); + // Prepare a supplier of package parser for the staging manager to parse apex file + // during the staging installation. + final Supplier<PackageParser2> apexParserSupplier = () -> new PackageParser2( + mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, mPackageParserCallback); + mInstallerService = new PackageInstallerService(mContext, this, apexParserSupplier); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { @@ -9679,6 +9684,20 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, String loaderIsa) { + if (PLATFORM_PACKAGE_NAME.equals(loadingPackageName) + && Binder.getCallingUid() != Process.SYSTEM_UID) { + Slog.w(TAG, "Non System Server process reporting dex loads as system server. uid=" + + Binder.getCallingUid()); + // Do not record dex loads from processes pretending to be system server. + // Only the system server should be assigned the package "android", so reject calls + // that don't satisfy the constraint. + // + // notifyDexLoad is a PM API callable from the app process. So in theory, apps could + // craft calls to this API and pretend to be system server. Doing so poses no particular + // danger for dex load reporting or later dexopt, however it is a sensible check to do + // in order to verify the expectations. + return; + } int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -9836,6 +9855,11 @@ public class PackageManagerService extends IPackageManager.Stub private int performDexOptInternalWithDependenciesLI(AndroidPackage p, @NonNull PackageSetting pkgSetting, DexoptOptions options) { + // System server gets a special path. + if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) { + return mDexManager.dexoptSystemServer(options); + } + // Select the dex optimizer based on the force parameter. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. @@ -11305,7 +11329,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.i(TAG, "Using ABIS and native lib paths from settings : " + parsedPackage.getPackageName() + " " + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage) - + ", " + + ", " + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage)); } } @@ -17534,31 +17558,26 @@ public class PackageManagerService extends IPackageManager.Stub + " Activities needs verification ..."); int count = 0; - boolean handlesWebUris = false; - final boolean alreadyVerified; + synchronized (mLock) { // If this is a new install and we see that we've already run verification for this // package, we have nothing to do: it means the state was restored from backup. - final IntentFilterVerificationInfo ivi = - mSettings.getIntentFilterVerificationLPr(packageName); - alreadyVerified = (ivi != null); - if (!replacing && alreadyVerified) { - if (DEBUG_DOMAIN_VERIFICATION) { - Slog.i(TAG, "Package " + packageName + " already verified: status=" - + ivi.getStatusString()); + if (!replacing) { + IntentFilterVerificationInfo ivi = + mSettings.getIntentFilterVerificationLPr(packageName); + if (ivi != null) { + if (DEBUG_DOMAIN_VERIFICATION) { + Slog.i(TAG, "Package " + packageName+ " already verified: status=" + + ivi.getStatusString()); + } + return; } - return; } - // If any filters need to be verified, then all need to be. In addition, we need to - // know whether an updating app has any web navigation intent filters, to re- - // examine handling policy even if not re-verifying. + // If any filters need to be verified, then all need to be. boolean needToVerify = false; for (ParsedActivity a : activities) { for (ParsedIntentInfo filter : a.getIntents()) { - if (filter.handlesWebUris(true)) { - handlesWebUris = true; - } if (filter.needsVerification() && needsNetworkVerificationLPr(a.getPackageName())) { if (DEBUG_DOMAIN_VERIFICATION) { @@ -17566,23 +17585,19 @@ public class PackageManagerService extends IPackageManager.Stub "Intent filter needs verification, so processing all filters"); } needToVerify = true; - // It's safe to break out here because filter.needsVerification() - // can only be true if filter.handlesWebUris(true) returns true, so - // we've already noted that. break; } } } - // Note whether this app publishes any web navigation handling support at all, - // and whether there are any web-nav filters that fit the profile for running - // a verification pass now. if (needToVerify) { + final boolean needsVerification = needsNetworkVerificationLPr(packageName); final int verificationId = mIntentFilterVerificationToken++; for (ParsedActivity a : activities) { for (ParsedIntentInfo filter : a.getIntents()) { - if (filter.handlesWebUris(true) - && needsNetworkVerificationLPr(a.getPackageName())) { + // Run verification against hosts mentioned in any web-nav intent filter, + // even if the filter matches non-web schemes as well + if (needsVerification && filter.handlesWebUris(false)) { if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString()); mIntentFilterVerifier.addOneIntentFilterVerification( @@ -17595,23 +17610,13 @@ public class PackageManagerService extends IPackageManager.Stub } if (count > 0) { - // count > 0 means that we're running a full verification pass if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count + " IntentFilter verification" + (count > 1 ? "s" : "") + " for userId:" + userId); mIntentFilterVerifier.startVerifications(userId); - } else if (alreadyVerified && handlesWebUris) { - // App used autoVerify in the past, no longer does, but still handles web - // navigation starts. - if (DEBUG_DOMAIN_VERIFICATION) { - Slog.d(TAG, "App changed web filters but no longer verifying - resetting policy"); - } - synchronized (mLock) { - clearIntentFilterVerificationsLPw(packageName, userId); - } } else { if (DEBUG_DOMAIN_VERIFICATION) { - Slog.d(TAG, "No web filters or no prior verify policy for " + packageName); + Slog.d(TAG, "No filters or not all autoVerify for " + packageName); } } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 091535dfc792..f6e4e1f26bd5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -1291,7 +1291,6 @@ public final class Settings { return false; } ps.clearDomainVerificationStatusForUser(userId); - ps.setIntentFilterVerificationInfo(null); return true; } @@ -4668,6 +4667,10 @@ public final class Settings { pw.print(prefix); pw.print(" privateFlags="); printFlags(pw, privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println(); } + if (pkg.hasPreserveLegacyExternalStorage()) { + pw.print(prefix); pw.print(" hasPreserveLegacyExternalStorage=true"); + pw.println(); + } pw.print(prefix); pw.print(" forceQueryable="); pw.print(ps.pkg.isForceQueryable()); if (ps.forceQueryableOverride) { pw.print(" (override=true)"); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 60292b45c0ee..8f6bd026a9bd 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -35,11 +35,11 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; +import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -68,8 +68,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.rollback.WatchdogRollbackLogger; import java.io.File; @@ -80,6 +82,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; /** * This class handles staged install sessions, i.e. install sessions that require packages to @@ -94,6 +97,7 @@ public class StagingManager { private final PowerManager mPowerManager; private final Context mContext; private final PreRebootVerificationHandler mPreRebootVerificationHandler; + private final Supplier<PackageParser2> mPackageParserSupplier; @GuardedBy("mStagedSessions") private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); @@ -105,9 +109,11 @@ public class StagingManager { private final List<String> mFailedPackageNames = new ArrayList<>(); private String mNativeFailureReason; - StagingManager(PackageInstallerService pi, Context context) { + StagingManager(PackageInstallerService pi, Context context, + Supplier<PackageParser2> packageParserSupplier) { mPi = pi; mContext = context; + mPackageParserSupplier = packageParserSupplier; mApexManager = ApexManager.getInstance(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -223,17 +229,21 @@ public class StagingManager { final List<String> apexPackageNames = new ArrayList<>(); for (ApexInfo apexInfo : apexInfoList.apexInfos) { final PackageInfo packageInfo; - int flags = PackageManager.GET_META_DATA; - PackageParser.Package pkg; - try { + final int flags = PackageManager.GET_META_DATA; + try (PackageParser2 packageParser = mPackageParserSupplier.get()) { File apexFile = new File(apexInfo.modulePath); - PackageParser pp = new PackageParser(); - pkg = pp.parsePackage(apexFile, flags, false); + final ParsedPackage parsedPackage = packageParser.parsePackage( + apexFile, flags, false); + packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags); + if (packageInfo == null) { + throw new PackageManagerException( + SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Unable to generate package info: " + apexInfo.modulePath); + } } catch (PackageParserException e) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexInfo.modulePath, e); } - packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { @@ -254,8 +264,9 @@ public class StagingManager { private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException { RollbackManager rm = mContext.getSystemService(RollbackManager.class); - List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks(); - for (RollbackInfo rollback : rollbacks) { + final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks(); + for (int i = 0, size = rollbacks.size(); i < size; i++) { + final RollbackInfo rollback = rollbacks.get(i); if (rollback.getCommittedSessionId() == sessionId) { return rollback.getRollbackId(); } @@ -388,7 +399,8 @@ public class StagingManager { } } } - for (PackageInstallerSession childSession : childrenSessions) { + for (int i = 0, size = childrenSessions.size(); i < size; i++) { + final PackageInstallerSession childSession = childrenSessions.get(i); if (sessionContainsApex(childSession)) { apexSessions.add(childSession); } @@ -402,15 +414,15 @@ public class StagingManager { IRollbackManager rm = IRollbackManager.Stub.asInterface( ServiceManager.getService(Context.ROLLBACK_SERVICE)); - for (PackageInstallerSession apexSession : apexSessions) { - String packageName = apexSession.getPackageName(); + for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) { + final String packageName = apexSessions.get(i).getPackageName(); // Perform any snapshots or restores for the APEX itself snapshotAndRestoreApexUserData(packageName, allUsers, rm); // Process the apks inside the APEX - List<String> apksInApex = mApexManager.getApksInApex(packageName); - for (String apk: apksInApex) { - snapshotAndRestoreApkInApexUserData(apk, allUsers, rm); + final List<String> apksInApex = mApexManager.getApksInApex(packageName); + for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) { + snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm); } } } @@ -637,7 +649,8 @@ public class StagingManager { 0 /* UserHandle.SYSTEM */); PackageInstallerSession apkSession = mPi.getSession(apkSessionId); apkSession.open(); - for (String apkFilePath : apkFilePaths) { + for (int i = 0, size = apkFilePaths.size(); i < size; i++) { + final String apkFilePath = apkFilePaths.get(i); File apkFile = new File(apkFilePath); ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY); @@ -705,9 +718,9 @@ public class StagingManager { "Unable to prepare multi-package session for staged session"); } - for (PackageInstallerSession sessionToClone : childSessions) { - PackageInstallerSession apkChildSession = - createAndWriteApkSession(sessionToClone, preReboot); + for (int i = 0, size = childSessions.size(); i < size; i++) { + final PackageInstallerSession apkChildSession = createAndWriteApkSession( + childSessions.get(i), preReboot); try { apkParentSession.addChildSessionId(apkChildSession.sessionId); } catch (IllegalStateException e) { @@ -1206,10 +1219,9 @@ public class StagingManager { // multi-package sessions, find all the child sessions that contain an APEX. if (hasApex) { try { - final List<PackageInfo> apexPackages = - submitSessionToApexService(session); - for (PackageInfo apexPackage : apexPackages) { - validateApexSignature(apexPackage); + final List<PackageInfo> apexPackages = submitSessionToApexService(session); + for (int i = 0, size = apexPackages.size(); i < size; i++) { + validateApexSignature(apexPackages.get(i)); } } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d2443fa735e9..323ffcfc2a1c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1716,27 +1716,6 @@ public class UserManagerService extends IUserManager.Stub { } } - private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId, - @Nullable Bundle restrictions, - @UserManagerInternal.OwnerType int restrictionOwnerType) { - final Bundle global = new Bundle(); - final Bundle local = new Bundle(); - - // Sort restrictions into local and global ensuring they don't overlap. - UserRestrictionsUtils.sortToGlobalAndLocal(restrictions, restrictionOwnerType, global, - local); - boolean isDeviceOwner = restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER; - - RestrictionsSet localRestrictionsSet; - if (UserRestrictionsUtils.isEmpty(local)) { - localRestrictionsSet = new RestrictionsSet(); - } else { - localRestrictionsSet = new RestrictionsSet(originatingUserId, local); - } - setDevicePolicyUserRestrictionsInner(originatingUserId, global, localRestrictionsSet, - isDeviceOwner); - } - /** * See {@link UserManagerInternal#setDevicePolicyUserRestrictions} */ @@ -4754,10 +4733,10 @@ public class UserManagerService extends IUserManager.Stub { private class LocalService extends UserManagerInternal { @Override public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId, - @Nullable Bundle restrictions, - @OwnerType int restrictionOwnerType) { + @NonNull Bundle global, @NonNull RestrictionsSet local, + boolean isDeviceOwner) { UserManagerService.this.setDevicePolicyUserRestrictionsInner(originatingUserId, - restrictions, restrictionOwnerType); + global, local, isDeviceOwner); } @Override diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index eec6e024fef2..c0502b8a068c 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -233,6 +233,13 @@ public class UserRestrictionsUtils { ); /** + * Special user restrictions that profile owner of an organization-owned managed profile can + * set on the parent profile instance to apply them on the personal profile. + */ + private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS = + Sets.newArraySet(); + + /** * User restrictions that default to {@code true} for managed profile owners. * * NB: {@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES} is also set by default but it is @@ -416,7 +423,8 @@ public class UserRestrictionsUtils { * @return true if a restriction is settable by profile owner of an organization owned device. */ public static boolean canProfileOwnerOfOrganizationOwnedDeviceChange(String restriction) { - return PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(restriction); + return PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(restriction) + || PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS.contains(restriction); } /** @@ -427,31 +435,9 @@ public class UserRestrictionsUtils { } /** - * Takes restrictions that can be set by device owner, and sort them into what should be applied - * globally and what should be applied only on the current user. - */ - public static void sortToGlobalAndLocal(@Nullable Bundle in, - @UserManagerInternal.OwnerType int restrictionOwnerType, @NonNull Bundle global, - @NonNull Bundle local) { - if (in == null || in.size() == 0) { - return; - } - for (String key : in.keySet()) { - if (!in.getBoolean(key)) { - continue; - } - if (isGlobal(restrictionOwnerType, key)) { - global.putBoolean(key, true); - } else { - local.putBoolean(key, true); - } - } - } - - /** * Whether given user restriction should be enforced globally. */ - private static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType, + public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType, String key) { return ((restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) && ( PRIMARY_USER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key))) @@ -463,6 +449,14 @@ public class UserRestrictionsUtils { } /** + * Whether given user restriction should be enforced locally. + */ + public static boolean isLocal(@UserManagerInternal.OwnerType int restrictionOwnerType, + String key) { + return !isGlobal(restrictionOwnerType, key); + } + + /** * @return true if two Bundles contain the same user restriction. * A null bundle and an empty bundle are considered to be equal. */ diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 117cc5e8eb80..eb7057dd2994 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -17,13 +17,17 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static java.util.function.Function.identity; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackagePartitions; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; @@ -45,6 +49,9 @@ import dalvik.system.VMRuntime; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -66,13 +73,12 @@ import java.util.zip.ZipEntry; */ public class DexManager { private static final String TAG = "DexManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = "pm.dexopt.priv-apps-oob-list"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final Context mContext; // Maps package name to code locations. @@ -103,19 +109,6 @@ public class DexManager { private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex - /** - * We do not record packages that have no secondary dex files or that are not used by other - * apps. This is an optimization to reduce the amount of data that needs to be written to - * disk (apps will not usually be shared so this trims quite a bit the number we record). - * - * To make this behaviour transparent to the callers which need use information on packages, - * DexManager will return this DEFAULT instance from - * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and - * is marked as not being used by other apps. This reflects the intended behaviour when we don't - * find the package in the underlying data file. - */ - private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); - public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock) { mContext = context; @@ -190,10 +183,14 @@ public class DexManager { boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; - if (primaryOrSplit && !isUsedByOtherApps) { + if (primaryOrSplit && !isUsedByOtherApps + && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. + // Note this is just an optimization that makes things easier to read in the + // package-dex-use file since we don't need to pollute it with redundant info. + // However, we always record system server packages. continue; } @@ -211,7 +208,7 @@ public class DexManager { // async write to disk to make sure we don't loose the data in case of a reboot. if (mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + dexPath, loaderUserId, loaderIsa, primaryOrSplit, loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } @@ -227,6 +224,23 @@ public class DexManager { } /** + * Check if the dexPath belongs to system server. + * System server can load code from different location, so we cast a wide-net here, and + * assume that if the paths is on any of the registered system partitions then it can be loaded + * by system server. + */ + private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { + ArrayList<PackagePartitions.SystemPartition> partitions = + PackagePartitions.getOrderedPartitions(identity()); + for (int i = 0; i < partitions.size(); i++) { + if (partitions.get(i).containsPath(dexPath)) { + return true; + } + } + return false; + } + + /** * Read the dex usage from disk and populate the code cache locations. * @param existingPackages a map containing information about what packages * are available to what users. Only packages in this list will be @@ -364,7 +378,9 @@ public class DexManager { try { mPackageDexUsage.read(); - mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + List<String> packagesToKeepDataAbout = new ArrayList<>(); + mPackageDexUsage.syncData( + packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout); } catch (Exception e) { mPackageDexUsage.clear(); Slog.w(TAG, "Exception while loading package dex usage. " @@ -391,8 +407,17 @@ public class DexManager { * to access the package use. */ public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { + // We do not record packages that have no secondary dex files or that are not used by other + // apps. This is an optimization to reduce the amount of data that needs to be written to + // disk (apps will not usually be shared so this trims quite a bit the number we record). + // + // To make this behaviour transparent to the callers which need use information on packages, + // DexManager will return this DEFAULT instance from + // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files + // and is marked as not being used by other apps. This reflects the intended behaviour when + // we don't find the package in the underlying data file. PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); - return useInfo == null ? DEFAULT_USE_INFO : useInfo; + return useInfo == null ? new PackageUseInfo(packageName) : useInfo; } /** @@ -414,15 +439,7 @@ public class DexManager { * because they don't need to be compiled).. */ public boolean dexoptSecondaryDex(DexoptOptions options) { - // Select the dex optimizer based on the force parameter. - // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust - // the necessary dexopt flags to make sure that compilation is not skipped. This avoid - // passing the force flag through the multitude of layers. - // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to - // allocate an object here. - PackageDexOptimizer pdo = options.isForce() - ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) - : mPackageDexOptimizer; + PackageDexOptimizer pdo = getPackageDexOptimizer(options); String packageName = options.getPackageName(); PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo.getDexUseInfoMap().isEmpty()) { @@ -463,6 +480,83 @@ public class DexManager { } /** + * Performs dexopt on system server dex files. + * + * <p>Verfifies that the package name is {@link PackageManagerService#PLATFORM_PACKAGE_NAME}. + * + * @return + * <p>PackageDexOptimizer.DEX_OPT_SKIPPED if dexopt was skipped because no system server + * files were recorded or if no dexopt was needed. + * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed. + * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded. + */ + public int dexoptSystemServer(DexoptOptions options) { + if (!PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { + Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:" + + options.getPackageName()); + return PackageDexOptimizer.DEX_OPT_FAILED; + } + + PackageDexOptimizer pdo = getPackageDexOptimizer(options); + String packageName = options.getPackageName(); + PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); + if (useInfo.getDexUseInfoMap().isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "No dex files recorded for system server"); + } + // Nothing to compile, return true. + return PackageDexOptimizer.DEX_OPT_SKIPPED; + } + + boolean usageUpdated = false; + int result = PackageDexOptimizer.DEX_OPT_SKIPPED; + for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { + String dexPath = entry.getKey(); + DexUseInfo dexUseInfo = entry.getValue(); + if (!Files.exists(Paths.get(dexPath))) { + if (DEBUG) { + Slog.w(TAG, "A dex file previously loaded by System Server does not exist " + + " anymore: " + dexPath); + } + usageUpdated = mPackageDexUsage.removeDexFile( + packageName, dexPath, dexUseInfo.getOwnerUserId()) || usageUpdated; + continue; + } + + int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, options); + + // The end result is: + // - FAILED if any path failed, + // - PERFORMED if at least one path needed compilation, + // - SKIPPED when all paths are up to date + if ((result != PackageDexOptimizer.DEX_OPT_FAILED) + && (newResult != PackageDexOptimizer.DEX_OPT_SKIPPED)) { + result = newResult; + } + } + + if (usageUpdated) { + mPackageDexUsage.maybeWriteAsync(); + } + + return result; + } + + /** + * Select the dex optimizer based on the force parameter. + * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust + * the necessary dexopt flags to make sure that compilation is not skipped. This avoid + * passing the force flag through the multitude of layers. + * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to + * allocate an object here. + */ + private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) { + return options.isForce() + ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) + : mPackageDexOptimizer; + } + + /** * Reconcile the information we have about the secondary dex files belonging to * {@code packagName} and the actual dex files. For all dex files that were * deleted, update the internal records and delete any generated oat files. @@ -542,7 +636,7 @@ public class DexManager { // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the // compilation happening here will use a pessimistic context. public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, - boolean isUsedByOtherApps, int userId) { + boolean isSharedModule, int userId) { // Find the owning package record. DexSearchResult searchResult = getDexPackage(info, dexPath, userId); @@ -559,11 +653,14 @@ public class DexManager { // We found the package. Now record the usage for all declared ISAs. boolean update = false; + // If this is a shared module set the loading package to an arbitrary package name + // so that we can mark that module as usedByOthers. + String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName; for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, + dexPath, userId, isa, /*primaryOrSplit*/ false, searchResult.mOwningPackageName, - PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); update |= newUpdate; } if (update) { @@ -603,12 +700,6 @@ public class DexManager { */ private DexSearchResult getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId) { - // Ignore framework code. - // TODO(calin): is there a better way to detect it? - if (dexPath.startsWith("/system/framework/")) { - return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); - } - // First, check if the package which loads the dex file actually owns it. // Most of the time this will be true and we can return early. PackageCodeLocations loadingPackageCodeLocations = @@ -631,6 +722,28 @@ public class DexManager { } } + // We could not find the owning package amongst regular apps. + // If the loading package is system server, see if the dex file resides + // on any of the potentially system server owning location and if so, + // assuming system server ownership. + // + // Note: We don't have any way to detect which code paths are actually + // owned by system server. We can only assume that such paths are on + // system partitions. + if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) { + if (isSystemServerDexPathSupportedForOdex(dexPath)) { + // We record system server dex files as secondary dex files. + // The reason is that we only record the class loader context for secondary dex + // files and we expect that all primary apks are loaded with an empty class loader. + // System server dex files may be loaded in non-empty class loader so we need to + // keep track of their context. + return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY); + } else { + Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: " + + dexPath); + } + } + if (DEBUG) { // TODO(calin): Consider checking for /data/data symlink. // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 08763e729c71..10760f52a02b 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -39,10 +39,12 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -51,25 +53,21 @@ import java.util.Set; * Stat file which store usage information about dex files. */ public class PackageDexUsage extends AbstractStatsBase<Void> { - private final static String TAG = "PackageDexUsage"; + private static final String TAG = "PackageDexUsage"; - // We support previous version to ensure that the usage list remains valid cross OTAs. - private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; - // Version 2 added: - // - the list of packages that load the dex files - // - class loader contexts for secondary dex files - // - usage for all code paths (including splits) - private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2 = 2; + // We are currently at version 2. + // Version 1 was introduced in Nougat and Version 2 in Oreo. + // We dropped version 1 support in R since all devices should have updated + // already. + private static final int PACKAGE_DEX_USAGE_VERSION = 2; - private final static int PACKAGE_DEX_USAGE_VERSION = PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; - - private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = + private static final String PACKAGE_DEX_USAGE_VERSION_HEADER = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; - private final static String SPLIT_CHAR = ","; - private final static String CODE_PATH_LINE_CHAR = "+"; - private final static String DEX_LINE_CHAR = "#"; - private final static String LOADING_PACKAGE_CHAR = "@"; + private static final String SPLIT_CHAR = ","; + private static final String CODE_PATH_LINE_CHAR = "+"; + private static final String DEX_LINE_CHAR = "#"; + private static final String LOADING_PACKAGE_CHAR = "@"; // One of the things we record about dex files is the class loader context that was used to // load them. That should be stable but if it changes we don't keep track of variable contexts. @@ -77,10 +75,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // skip optimizations on that dex files. /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = "=VariableClassLoaderContext="; - // The markers used for unknown class loader contexts. This can happen if the dex file was - // recorded in a previous version and we didn't have a chance to update its usage. - /*package*/ static final String UNKNOWN_CLASS_LOADER_CONTEXT = - "=UnknownClassLoaderContext="; // The marker used for unsupported class loader contexts (no longer written, may occur in old // files so discarded on read). Note: this matches @@ -126,7 +120,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * has been seen before. */ /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, + String loaderIsa, boolean primaryOrSplit, String loadingPackageName, String classLoaderContext) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); @@ -135,20 +129,22 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { throw new IllegalArgumentException("Null classLoaderContext"); } if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { + Slog.e(TAG, "Unsupported context?"); return false; } + boolean isUsedByOtherApps = !owningPackageName.equals(loadingPackageName); + synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); if (packageUseInfo == null) { // This is the first time we see the package. - packageUseInfo = new PackageUseInfo(); + packageUseInfo = new PackageUseInfo(owningPackageName); if (primaryOrSplit) { // If we have a primary or a split apk, set isUsedByOtherApps. // We do not need to record the loaderIsa or the owner because we compile // primaries for all users and all ISAs. - packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, - owningPackageName, loadingPackageName); + packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); } else { // For secondary dex files record the loaderISA and the owner. We'll need // to know under which user to compile and for what ISA. @@ -164,9 +160,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // We already have data on this package. Amend it. if (primaryOrSplit) { // We have a possible update on the primary apk usage. Merge - // isUsedByOtherApps information and return if there was an update. - return packageUseInfo.mergeCodePathUsedByOtherApps( - dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName); + // dex path information and return if there was an update. + return packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); } else { DexUseInfo newData = new DexUseInfo( isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); @@ -281,7 +276,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // Write the code paths used by other apps. for (Map.Entry<String, Set<String>> codeEntry : - packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { + packageUseInfo.mPrimaryCodePaths.entrySet()) { String codePath = codeEntry.getKey(); Set<String> loadingPackages = codeEntry.getValue(); fpw.println(CODE_PATH_LINE_CHAR + codePath); @@ -339,7 +334,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { version = Integer.parseInt( versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); if (!isSupportedVersion(version)) { - throw new IllegalStateException("Unexpected version: " + version); + Slog.w(TAG, "Unexpected package-dex-use version: " + version + + ". Not reading from it"); + return; } } @@ -377,9 +374,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { throw new IllegalStateException("Invalid PackageDexUsage line: " + line); } - // In version 2 we added the loading packages and class loader context. - Set<String> loadingPackages = maybeReadLoadingPackages(in, version); - String classLoaderContext = maybeReadClassLoaderContext(in, version); + Set<String> loadingPackages = readLoadingPackages(in, version); + String classLoaderContext = readClassLoaderContext(in, version); if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(classLoaderContext)) { // We used to record use of unsupported class loaders, but we no longer do. @@ -410,34 +406,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); } else if (line.startsWith(CODE_PATH_LINE_CHAR)) { - // This is a code path used by other apps line. - if (version < PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { - throw new IllegalArgumentException("Unexpected code path line when parsing " + - "PackageDexUseData: " + line); - } - // Expects 2 lines: // +code_paths // @loading_packages String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); - Set<String> loadingPackages = maybeReadLoadingPackages(in, version); - currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); + Set<String> loadingPackages = readLoadingPackages(in, version); + currentPackageData.mPrimaryCodePaths.put(codePath, loadingPackages); } else { // This is a package line. - if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { - currentPackage = line; - currentPackageData = new PackageUseInfo(); - } else { - // Old version (<2) - // We expect it to be: `packageName,isUsedByOtherApps`. - String[] elems = line.split(SPLIT_CHAR); - if (elems.length != 2) { - throw new IllegalStateException("Invalid PackageDexUsage line: " + line); - } - currentPackage = elems[0]; - currentPackageData = new PackageUseInfo(); - currentPackageData.mUsedByOtherAppsBeforeUpgrade = readBoolean(elems[1]); - } + currentPackage = line; + currentPackageData = new PackageUseInfo(currentPackage); data.put(currentPackage, currentPackageData); } } @@ -449,46 +427,31 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } /** - * Reads the class loader context encoding from the buffer {@code in} if - * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. + * Reads the class loader context encoding from the buffer {@code in}. */ - private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { - String context = null; - if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { - context = in.readLine(); - if (context == null) { - throw new IllegalStateException("Could not find the classLoaderContext line."); - } + private String readClassLoaderContext(BufferedReader in, int version) throws IOException { + String context = in.readLine(); + if (context == null) { + throw new IllegalStateException("Could not find the classLoaderContext line."); } - // The context might be empty if we didn't have the chance to update it after a version - // upgrade. In this case return the special marker so that we recognize this is an unknown - // context. - return context == null ? UNKNOWN_CLASS_LOADER_CONTEXT : context; + return context; } /** - * Reads the list of loading packages from the buffer {@code in} if - * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. + * Reads the list of loading packages from the buffer {@code in}. */ - private Set<String> maybeReadLoadingPackages(BufferedReader in, int version) + private Set<String> readLoadingPackages(BufferedReader in, int version) throws IOException { - if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { - String line = in.readLine(); - if (line == null) { - throw new IllegalStateException("Could not find the loadingPackages line."); - } - // We expect that most of the times the list of loading packages will be empty. - if (line.length() == LOADING_PACKAGE_CHAR.length()) { - return Collections.emptySet(); - } else { - Set<String> result = new HashSet<>(); - Collections.addAll(result, - line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); - return result; - } - } else { - return Collections.emptySet(); + String line = in.readLine(); + if (line == null) { + throw new IllegalStateException("Could not find the loadingPackages line."); + } + Set<String> result = new HashSet<>(); + if (line.length() != LOADING_PACKAGE_CHAR.length()) { + Collections.addAll(result, + line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); } + return result; } /** @@ -501,21 +464,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } private boolean isSupportedVersion(int version) { - return version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 - || version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; + return version == PACKAGE_DEX_USAGE_VERSION; } /** * Syncs the existing data with the set of available packages by removing obsolete entries. */ /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, - Map<String, Set<String>> packageToCodePaths) { + Map<String, Set<String>> packageToCodePaths, + List<String> packagesToKeepDataAbout) { synchronized (mPackageUseInfoMap) { Iterator<Map.Entry<String, PackageUseInfo>> pIt = mPackageUseInfoMap.entrySet().iterator(); while (pIt.hasNext()) { Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); String packageName = pEntry.getKey(); + if (packagesToKeepDataAbout.contains(packageName)) { + // This is a package for which we should keep the data even if it's not + // in the list of user packages. + continue; + } PackageUseInfo packageUseInfo = pEntry.getValue(); Set<Integer> users = packageToUsersMap.get(packageName); if (users == null) { @@ -536,22 +504,31 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // Sync the code paths. Set<String> codePaths = packageToCodePaths.get(packageName); - Iterator<Map.Entry<String, Set<String>>> codeIt = - packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); - while (codeIt.hasNext()) { - if (!codePaths.contains(codeIt.next().getKey())) { - codeIt.remove(); + + Iterator<Map.Entry<String, Set<String>>> recordedIt = + packageUseInfo.mPrimaryCodePaths.entrySet().iterator(); + while (recordedIt.hasNext()) { + Map.Entry<String, Set<String>> entry = recordedIt.next(); + String recordedCodePath = entry.getKey(); + if (!codePaths.contains(recordedCodePath)) { + // Clean up a non existing code path. + recordedIt.remove(); + } else { + // Clean up a non existing loading package. + Set<String> recordedLoadingPackages = entry.getValue(); + Iterator<String> recordedLoadingPackagesIt = + recordedLoadingPackages.iterator(); + while (recordedLoadingPackagesIt.hasNext()) { + String recordedLoadingPackage = recordedLoadingPackagesIt.next(); + if (!packagesToKeepDataAbout.contains(recordedLoadingPackage) + && !packageToUsersMap.containsKey(recordedLoadingPackage)) { + recordedLoadingPackagesIt.remove(); + } + } } } - // In case the package was marked as used by other apps in a previous version - // propagate the flag to all the code paths. - // See mUsedByOtherAppsBeforeUpgrade docs on why it is important to do it. - if (packageUseInfo.mUsedByOtherAppsBeforeUpgrade) { - for (String codePath : codePaths) { - packageUseInfo.mergeCodePathUsedByOtherApps(codePath, true, null, null); - } - } else if (!packageUseInfo.isAnyCodePathUsedByOtherApps() + if (!packageUseInfo.isAnyCodePathUsedByOtherApps() && packageUseInfo.mDexUseInfoMap.isEmpty()) { // The package is not used by other apps and we removed all its dex files // records. Remove the entire package record as well. @@ -712,35 +689,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Stores data on how a package and its dex files are used. */ public static class PackageUseInfo { + // The name of the package this info belongs to. + private final String mPackageName; // The app's code paths that are used by other apps. // The key is the code path and the value is the set of loading packages. - private final Map<String, Set<String>> mCodePathsUsedByOtherApps; + private final Map<String, Set<String>> mPrimaryCodePaths; // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). private final Map<String, DexUseInfo> mDexUseInfoMap; - // Keeps track of whether or not this package was used by other apps before - // we upgraded to VERSION 4 which records the info for each code path separately. - // This is unwanted complexity but without it we risk to profile guide compile - // something that supposed to be shared. For example: - // 1) we determine that chrome is used by another app - // 2) we take an OTA which upgrades the way we keep track of usage data - // 3) chrome doesn't get used until the background job executes - // 4) as part of the backgound job we now think that chrome is not used by others - // and we speed-profile. - // 5) as a result the next time someone uses chrome it will extract from apk since - // the compiled code will be private. - private boolean mUsedByOtherAppsBeforeUpgrade; - - /*package*/ PackageUseInfo() { - mCodePathsUsedByOtherApps = new HashMap<>(); + /*package*/ PackageUseInfo(String packageName) { + mPrimaryCodePaths = new HashMap<>(); mDexUseInfoMap = new HashMap<>(); + mPackageName = packageName; } // Creates a deep copy of the `other`. private PackageUseInfo(PackageUseInfo other) { - mCodePathsUsedByOtherApps = new HashMap<>(); - for (Map.Entry<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) { - mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); + mPackageName = other.mPackageName; + mPrimaryCodePaths = new HashMap<>(); + for (Map.Entry<String, Set<String>> e : other.mPrimaryCodePaths.entrySet()) { + mPrimaryCodePaths.put(e.getKey(), new HashSet<>(e.getValue())); } mDexUseInfoMap = new HashMap<>(); @@ -749,28 +717,29 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } } - private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, - String owningPackageName, String loadingPackage) { - if (!isUsedByOtherApps) { - // Nothing to update if the the code path is not used by other apps. - return false; - } - - boolean newCodePath = false; - Set<String> loadingPackages = mCodePathsUsedByOtherApps.get(codePath); + private boolean mergePrimaryCodePaths(String codePath, String loadingPackage) { + Set<String> loadingPackages = mPrimaryCodePaths.get(codePath); if (loadingPackages == null) { loadingPackages = new HashSet<>(); - mCodePathsUsedByOtherApps.put(codePath, loadingPackages); - newCodePath = true; + mPrimaryCodePaths.put(codePath, loadingPackages); } - boolean newLoadingPackage = loadingPackage != null - && !loadingPackage.equals(owningPackageName) - && loadingPackages.add(loadingPackage); - return newCodePath || newLoadingPackage; + return loadingPackages.add(loadingPackage); } public boolean isUsedByOtherApps(String codePath) { - return mCodePathsUsedByOtherApps.containsKey(codePath); + if (mPrimaryCodePaths.containsKey(codePath)) { + Set<String> loadingPackages = mPrimaryCodePaths.get(codePath); + if (loadingPackages.contains(mPackageName)) { + // If the owning package is in the list then this code path + // is used by others if there are other packages in the list. + return loadingPackages.size() > 1; + } else { + // The owning package is not in the loading packages. So if + // the list is non-empty then the code path is used by others. + return !loadingPackages.isEmpty(); + } + } + return false; } public Map<String, DexUseInfo> getDexUseInfoMap() { @@ -778,11 +747,11 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } public Set<String> getLoadingPackages(String codePath) { - return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); + return mPrimaryCodePaths.getOrDefault(codePath, null); } public boolean isAnyCodePathUsedByOtherApps() { - return !mCodePathsUsedByOtherApps.isEmpty(); + return !mPrimaryCodePaths.isEmpty(); } /** @@ -790,16 +759,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Returns whether or not there was an update. */ /*package*/ boolean clearCodePathUsedByOtherApps() { - // Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with - // the new data. This is not saved to disk so we don't need to return it. - mUsedByOtherAppsBeforeUpgrade = true; - - if (mCodePathsUsedByOtherApps.isEmpty()) { - return false; - } else { - mCodePathsUsedByOtherApps.clear(); - return true; + boolean updated = false; + List<String> retainOnlyOwningPackage = new ArrayList<>(1); + retainOnlyOwningPackage.add(mPackageName); + for (Map.Entry<String, Set<String>> entry : mPrimaryCodePaths.entrySet()) { + // Remove or loading packages but the owning one. + if (entry.getValue().retainAll(retainOnlyOwningPackage)) { + updated = true; + } } + return updated; } } @@ -847,11 +816,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); String oldClassLoaderContext = mClassLoaderContext; - if (isUnknownOrUnsupportedContext(mClassLoaderContext)) { - // Can happen if we read a previous version. + if (isUnsupportedContext(mClassLoaderContext)) { mClassLoaderContext = dexUseInfo.mClassLoaderContext; - } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext) - && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { + } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { // We detected a context change. mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; } @@ -862,11 +829,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { || !Objects.equals(oldClassLoaderContext, mClassLoaderContext); } - private static boolean isUnknownOrUnsupportedContext(String context) { - // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases - // into UNSUPPORTED_CLASS_LOADER_CONTEXT. - return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context) - || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context); + private static boolean isUnsupportedContext(String context) { + return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context); } public boolean isUsedByOtherApps() { @@ -887,10 +851,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public String getClassLoaderContext() { return mClassLoaderContext; } - public boolean isUnknownClassLoaderContext() { - // The class loader context may be unknown if we loaded the data from a previous version - // which didn't save the context. - return isUnknownOrUnsupportedContext(mClassLoaderContext); + public boolean isUnsupportedClassLoaderContext() { + return isUnsupportedContext(mClassLoaderContext); } public boolean isVariableClassLoaderContext() { diff --git a/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java new file mode 100644 index 000000000000..807c82d887e3 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.dex; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +import android.content.pm.IPackageManager; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import dalvik.system.BaseDexClassLoader; +import dalvik.system.VMRuntime; + +import java.util.Map; + +/** + * Reports dex file use to the package manager on behalf of system server. + */ +public class SystemServerDexLoadReporter implements BaseDexClassLoader.Reporter { + private static final String TAG = "SystemServerDexLoadReporter"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final IPackageManager mPackageManager; + + private SystemServerDexLoadReporter(IPackageManager pm) { + mPackageManager = pm; + } + + @Override + public void report(Map<String, String> classLoaderContextMap) { + if (DEBUG) { + Slog.i(TAG, "Reporting " + classLoaderContextMap); + } + if (classLoaderContextMap.isEmpty()) { + Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap"); + return; + } + + try { + mPackageManager.notifyDexLoad( + PLATFORM_PACKAGE_NAME, + classLoaderContextMap, + VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException ignored) { + // We're in system server, it can't happen. + } + } + + /** + * Configures system server dex file reporting. + * <p>The method will install a reporter in the BaseDexClassLoader and also + * force the reporting of any dex files already loaded by the system server. + */ + public static void configureSystemServerDexReporter(IPackageManager pm) { + Slog.i(TAG, "Configuring system server dex reporter"); + + SystemServerDexLoadReporter reporter = new SystemServerDexLoadReporter(pm); + BaseDexClassLoader.setReporter(reporter); + ClassLoader currrentClassLoader = reporter.getClass().getClassLoader(); + if (currrentClassLoader instanceof BaseDexClassLoader) { + ((BaseDexClassLoader) currrentClassLoader).reportClassLoaderChain(); + } else { + Slog.wtf(TAG, "System server class loader is not a BaseDexClassLoader. type=" + + currrentClassLoader.getClass().getName()); + } + } +} diff --git a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java new file mode 100644 index 000000000000..ee3c406fc448 --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm.parsing.library; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +/** + * Updates a package to remove dependency on com.google.android.maps library. + * + * @hide + */ +@VisibleForTesting +public class ComGoogleAndroidMapsUpdater extends PackageSharedLibraryUpdater { + + private static final String LIBRARY_NAME = "com.google.android.maps"; + + @Override + public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) { + parsedPackage.removeUsesLibrary(LIBRARY_NAME); + parsedPackage.removeUsesOptionalLibrary(LIBRARY_NAME); + } +} diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index 1d018c485e08..1405a7d613f1 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -45,6 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { static { final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + // Remove com.google.android.maps library. + packageUpdaters.add(new ComGoogleAndroidMapsUpdater()); + // Automatically add the org.apache.http.legacy library to the app classpath if the app // targets < P. packageUpdaters.add(new OrgApacheHttpLegacyUpdater()); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index a726d39b8595..e3faffa0699b 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -882,7 +882,7 @@ public final class DefaultPermissionGrantPolicy { public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) { Log.i(TAG, "Granting permissions to default browser for user:" + userId); - grantPermissionsToSystemPackage(packageName, userId, ALWAYS_LOCATION_PERMISSIONS); + grantPermissionsToSystemPackage(packageName, userId, FOREGROUND_LOCATION_PERMISSIONS); } private String getDefaultSystemHandlerActivityPackage(String intentAction, int userId) { diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 161f30449a52..27288d852fb2 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; +import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -70,7 +72,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -180,8 +184,6 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); - - /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -232,7 +234,6 @@ public final class PermissionPolicyService extends SystemService { manager.updateUserSensitiveForApp(uid); } }, UserHandle.ALL, intentFilter, null, null); - */ } /** diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b41bc5c2dc51..5a3464d8a35f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -513,10 +513,7 @@ public final class PowerManagerService extends SystemService // The screen brightness setting override from the window manager // to allow the current foreground activity to override the brightness. - // Use -1 to disable. - private int mScreenBrightnessOverrideFromWindowManager = -1; - - private float mScreenBrightnessOverrideFromWindowManagerFloat = + private float mScreenBrightnessOverrideFromWindowManager = PowerManager.BRIGHTNESS_INVALID_FLOAT; // The window manager has determined the user to be inactive via other means. @@ -2846,7 +2843,7 @@ public final class PowerManagerService extends SystemService screenBrightnessOverride = mScreenBrightnessSettingDefault; } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { autoBrightness = false; - screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManagerFloat; + screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager; } else { autoBrightness = (mScreenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); @@ -2927,8 +2924,8 @@ public final class PowerManagerService extends SystemService return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress; } - private static boolean isValidBrightness(int value) { - return value >= 0 && value <= 255; + private static boolean isValidBrightness(float value) { + return value >= PowerManager.BRIGHTNESS_MIN && value <= PowerManager.BRIGHTNESS_MAX; } @VisibleForTesting @@ -3222,6 +3219,10 @@ public final class PowerManagerService extends SystemService private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm, @Nullable final String reason, boolean wait) { if (PowerManager.REBOOT_USERSPACE.equals(reason)) { + if (!PowerManager.isRebootingUserspaceSupportedImpl()) { + throw new UnsupportedOperationException( + "Attempted userspace reboot on a device that doesn't support it"); + } UserspaceRebootLogger.noteUserspaceRebootWasRequested(); } if (mHandler == null || !mSystemReady) { @@ -3583,13 +3584,11 @@ public final class PowerManagerService extends SystemService } } - // TODO(brightnessfloat): change to float - private void setScreenBrightnessOverrideFromWindowManagerInternal(int brightness) { + private void setScreenBrightnessOverrideFromWindowManagerInternal(float brightness) { synchronized (mLock) { - if (mScreenBrightnessOverrideFromWindowManager != brightness) { + if (!BrightnessSynchronizer.floatEquals(mScreenBrightnessOverrideFromWindowManager, + brightness)) { mScreenBrightnessOverrideFromWindowManager = brightness; - mScreenBrightnessOverrideFromWindowManagerFloat = - BrightnessSynchronizer.brightnessIntToFloat(mContext, brightness); mDirty |= DIRTY_SETTINGS; updatePowerStateLocked(); } @@ -3890,8 +3889,8 @@ public final class PowerManagerService extends SystemService + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")"); pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting); pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting); - pw.println(" mScreenBrightnessOverrideFromWindowManagerFloat=" - + mScreenBrightnessOverrideFromWindowManagerFloat); + pw.println(" mScreenBrightnessOverrideFromWindowManager=" + + mScreenBrightnessOverrideFromWindowManager); pw.println(" mUserActivityTimeoutOverrideFromWindowManager=" + mUserActivityTimeoutOverrideFromWindowManager); pw.println(" mUserInactiveOverrideFromWindowManager=" @@ -4231,7 +4230,7 @@ public final class PowerManagerService extends SystemService proto.write( PowerServiceSettingsAndConfigurationDumpProto .SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER, - mScreenBrightnessOverrideFromWindowManagerFloat); + mScreenBrightnessOverrideFromWindowManager); proto.write( PowerServiceSettingsAndConfigurationDumpProto .USER_ACTIVITY_TIMEOUT_OVERRIDE_FROM_WINDOW_MANAGER_MS, @@ -5430,10 +5429,10 @@ public final class PowerManagerService extends SystemService @VisibleForTesting final class LocalService extends PowerManagerInternal { @Override - public void setScreenBrightnessOverrideFromWindowManager(int screenBrightness) { - if (screenBrightness < PowerManager.BRIGHTNESS_DEFAULT - || screenBrightness > PowerManager.BRIGHTNESS_ON) { - screenBrightness = PowerManager.BRIGHTNESS_DEFAULT; + public void setScreenBrightnessOverrideFromWindowManager(float screenBrightness) { + if (screenBrightness < PowerManager.BRIGHTNESS_MIN + || screenBrightness > PowerManager.BRIGHTNESS_MAX) { + screenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; } setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness); } diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 74958b692eb2..cf1bfc3b555f 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -21,10 +21,7 @@ "options": [ {"include-filter": "com.android.server.power"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - { - "exclude-filter": "com.android.server.power.PowerManagerServiceTest#testWakefulnessAwake_ShouldWakeUpWhenPluggedIn" - } + {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } ], diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 5f871ad4f9e4..83e99b008b68 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -505,6 +505,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { rollbackIds[i] = mRollbacks.get(i).info.getRollbackId(); } ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds); + try { + mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds); + } catch (Installer.InstallerException ie) { + Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie); + } } @WorkerThread diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 841aca5a8d6f..225bd82e9a21 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Build; +import android.os.Environment; import android.os.IBinder; import android.os.SystemProperties; import android.os.UserHandle; @@ -37,6 +38,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -131,12 +133,17 @@ public class FileIntegrityService extends SystemService { // duplicate the same loading logic here. // Load certificates trusted by the device manufacturer. - loadCertificatesFromDirectory("/product/etc/security/fsverity"); + // NB: Directories need to be synced with system/security/fsverity_init/fsverity_init.cpp. + final String relativeDir = "etc/security/fsverity"; + loadCertificatesFromDirectory(Environment.getRootDirectory().toPath() + .resolve(relativeDir)); + loadCertificatesFromDirectory(Environment.getProductDirectory().toPath() + .resolve(relativeDir)); } - private void loadCertificatesFromDirectory(String path) { + private void loadCertificatesFromDirectory(Path path) { try { - File[] files = new File(path).listFiles(); + File[] files = path.toFile().listFiles(); if (files == null) { return; } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 008933f643dd..da1b7f3113c4 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -114,6 +114,8 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware DEAD }; + private Boolean mCaptureState; + private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; private final @NonNull Context mContext; private Map<Integer, Set<ModuleService>> mModules; @@ -224,6 +226,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware mDelegate.setCaptureState(active); } catch (Exception e) { throw handleException(e); + } finally { + // It is safe to lock here - local operation. + synchronized (this) { + mCaptureState = active; + } } } @@ -274,8 +281,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware "This implementation is not inteded to be used directly with Binder."); } - @Override public void dump(PrintWriter pw) { + @Override + public void dump(PrintWriter pw) { synchronized (this) { + pw.printf("Capture state is %s\n", mCaptureState == null ? "uninitialized" + : (mCaptureState ? "active" : "inactive")); if (mModules != null) { for (int handle : mModules.keySet()) { pw.println("========================================="); diff --git a/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java new file mode 100644 index 000000000000..7cdb84ba0404 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Slog; +import android.util.StatsEvent; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; +import com.android.service.nano.StringListParamProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for creating {@link StatsEvent} data. + */ +final class SettingsStatsUtil { + private static final String TAG = "SettingsStatsUtil"; + private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{ + new FlagsData("GlobalFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("GlobalFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("GlobalFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("GlobalFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{ + new FlagsData("SecureFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SecureFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SecureFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SecureFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{ + new FlagsData("SystemFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SystemFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SystemFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SystemFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + + @VisibleForTesting + @NonNull + static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : GLOBAL_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Global.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SYSTEM_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.System.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SECURE_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Secure.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @VisibleForTesting + @Nullable + static StringListParamProto getList(String flag) { + final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag); + if (TextUtils.isEmpty(base64)) { + return null; + } + final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP); + StringListParamProto list = null; + try { + list = StringListParamProto.parseFrom(decode); + } catch (Exception e) { + Slog.e(TAG, "Error parsing string list proto", e); + } + return list; + } + + /** + * Create {@link StatsEvent} for SETTING_SNAPSHOT atom + */ + @NonNull + private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId, + int type) { + final StatsEvent.Builder builder = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(key); + boolean booleanValue = false; + int intValue = 0; + float floatValue = 0; + String stringValue = ""; + if (TextUtils.isEmpty(value)) { + builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } else { + switch (type) { + case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE: + booleanValue = "1".equals(value); + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE: + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE: + try { + floatValue = Float.parseFloat(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE: + stringValue = value; + break; + default: + Slog.w(TAG, "Unexpected value type " + type); + } + builder.writeInt(type) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } + return builder.build(); + } + + /** Class for defining flag name and its data type. */ + static final class FlagsData { + /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */ + String mFlagName; + /** Data type of the value getting from {@link Settings} keys. */ + int mDataType; + + FlagsData(String flagName, int dataType) { + mFlagName = flagName; + mDataType = dataType; + } + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e9da2c4c8472..288c22a94b45 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -416,6 +416,8 @@ public class StatsPullAtomService extends SystemService { return pullHealthHal(atomTag, data); case FrameworkStatsLog.ATTRIBUTED_APP_OPS: return pullAttributedAppOps(atomTag, data); + case FrameworkStatsLog.SETTING_SNAPSHOT: + return pullSettingsStats(atomTag, data); default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } @@ -580,6 +582,7 @@ public class StatsPullAtomService extends SystemService { registerFullBatteryCapacity(); registerBatteryVoltage(); registerBatteryCycleCount(); + registerSettingsStats(); } /** @@ -3244,6 +3247,43 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerSettingsStats() { + int tagId = FrameworkStatsLog.SETTING_SNAPSHOT; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mStatsCallbackImpl + ); + } + + int pullSettingsStats(int atomTag, List<StatsEvent> pulledData) { + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) { + return StatsManager.PULL_SKIP; + } + + final long token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : userManager.getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + if (userId == UserHandle.USER_SYSTEM) { + pulledData.addAll(SettingsStatsUtil.logGlobalSettings(mContext, atomTag, + UserHandle.USER_SYSTEM)); + } + pulledData.addAll(SettingsStatsUtil.logSystemSettings(mContext, atomTag, userId)); + pulledData.addAll(SettingsStatsUtil.logSecureSettings(mContext, atomTag, userId)); + } + } catch (Exception e) { + Slog.e(TAG, "failed to pullSettingsStats", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index d88dccb9afeb..26497d8b0290 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -131,7 +131,7 @@ public interface StatusBarManagerInternal { * @see com.android.internal.statusbar.IStatusBar#showToast(String, IBinder, CharSequence, * IBinder, int, ITransientNotificationCallback) */ - void showToast(String packageName, IBinder token, CharSequence text, + void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback textCallback); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 78ef68c09988..d7a0c9871b48 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -502,12 +502,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void showToast(String packageName, IBinder token, CharSequence text, + public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { if (mBar != null) { try { - mBar.showToast(packageName, token, text, windowToken, duration, callback); + mBar.showToast(uid, packageName, token, text, windowToken, duration, callback); } catch (RemoteException ex) { } } } diff --git a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java new file mode 100644 index 000000000000..d19a707770e2 --- /dev/null +++ b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.textclassifier; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.textclassifier.IconsUriHelper.ResourceInfo; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A content provider that is used to access icons returned from the TextClassifier service. + * + * <p>Use {@link IconsUriHelper#getContentUri(String, int)} to access a uri for a specific resource. + * The uri may be passed to other processes to access the specified resource. + * + * <p>NOTE: Care must be taken to avoid leaking resources to non-permitted apps via this provider. + */ +public final class IconsContentProvider extends ContentProvider { + + private static final String TAG = "IconsContentProvider"; + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) { + try { + final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri); + final Drawable drawable = Icon.createWithResource(res.packageName, res.id) + .loadDrawable(getContext()); + final byte[] data = getBitmapData(drawable); + final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + final ParcelFileDescriptor readSide = pipe[0]; + final ParcelFileDescriptor writeSide = pipe[1]; + try (OutputStream out = new AutoCloseOutputStream(writeSide)) { + out.write(data); + return readSide; + } + } catch (IOException | RuntimeException e) { + Log.e(TAG, "Error retrieving icon for uri: " + uri, e); + } + return null; + } + + /** + * Returns the bitmap data for the specified drawable. + */ + @VisibleForTesting + public static byte[] getBitmapData(Drawable drawable) { + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + throw new IllegalStateException("The icon is zero-sized"); + } + + final Bitmap bitmap = Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + final byte[] byteArray = stream.toByteArray(); + bitmap.recycle(); + return byteArray; + } + + @Override + public String getType(Uri uri) { + return "image/png"; + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/services/core/java/com/android/server/textclassifier/IconsUriHelper.java b/services/core/java/com/android/server/textclassifier/IconsUriHelper.java new file mode 100644 index 000000000000..f17b0f14bd0e --- /dev/null +++ b/services/core/java/com/android/server/textclassifier/IconsUriHelper.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.textclassifier; + +import android.annotation.Nullable; +import android.net.Uri; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Supplier; + +/** + * A helper for mapping an icon resource to a content uri. + * + * <p>NOTE: Care must be taken to avoid passing resource uris to non-permitted apps via this helper. + */ +@VisibleForTesting(visibility = Visibility.PACKAGE) +public final class IconsUriHelper { + + public static final String AUTHORITY = "com.android.textclassifier.icons"; + + private static final String TAG = "IconsUriHelper"; + private static final Supplier<String> DEFAULT_ID_SUPPLIER = () -> UUID.randomUUID().toString(); + + // TODO: Consider using an LRU cache to limit resource usage. + // This may depend on the expected number of packages that a device typically has. + @GuardedBy("mPackageIds") + private final Map<String, String> mPackageIds = new ArrayMap<>(); + + private final Supplier<String> mIdSupplier; + + private static final IconsUriHelper sSingleton = new IconsUriHelper(null); + + private IconsUriHelper(@Nullable Supplier<String> idSupplier) { + mIdSupplier = (idSupplier != null) ? idSupplier : DEFAULT_ID_SUPPLIER; + + // Useful for testing: + // Magic id for the android package so it is the same across classloaders. + // This is okay as this package does not have access restrictions, and + // the TextClassifierService hardly returns icons from this package. + mPackageIds.put("android", "android"); + } + + /** + * Returns a new instance of this object for testing purposes. + */ + public static IconsUriHelper newInstanceForTesting(@Nullable Supplier<String> idSupplier) { + return new IconsUriHelper(idSupplier); + } + + static IconsUriHelper getInstance() { + return sSingleton; + } + + /** + * Returns a Uri for the specified icon resource. + * + * @param packageName the resource's package name + * @param resId the resource id + * @see #getResourceInfo(Uri) + */ + public Uri getContentUri(String packageName, int resId) { + Objects.requireNonNull(packageName); + synchronized (mPackageIds) { + if (!mPackageIds.containsKey(packageName)) { + // TODO: Ignore packages that don't actually exist on the device. + mPackageIds.put(packageName, mIdSupplier.get()); + } + return new Uri.Builder() + .scheme("content") + .authority(AUTHORITY) + .path(mPackageIds.get(packageName)) + .appendPath(Integer.toString(resId)) + .build(); + } + } + + /** + * Returns a valid {@link ResourceInfo} for the specified uri. Returns {@code null} if a valid + * {@link ResourceInfo} cannot be returned for the specified uri. + * + * @see #getContentUri(String, int); + */ + @Nullable + public ResourceInfo getResourceInfo(Uri uri) { + if (!"content".equals(uri.getScheme())) { + return null; + } + if (!AUTHORITY.equals(uri.getAuthority())) { + return null; + } + + final List<String> pathItems = uri.getPathSegments(); + try { + synchronized (mPackageIds) { + final String packageId = pathItems.get(0); + final int resId = Integer.parseInt(pathItems.get(1)); + for (String packageName : mPackageIds.keySet()) { + if (packageId.equals(mPackageIds.get(packageName))) { + return new ResourceInfo(packageName, resId); + } + } + } + } catch (Exception e) { + Log.v(TAG, "Could not get resource info. Reason: " + e.getMessage()); + } + return null; + } + + /** + * A holder for a resource's package name and id. + */ + public static final class ResourceInfo { + + public final String packageName; + public final int id; + + private ResourceInfo(String packageName, int id) { + this.packageName = Objects.requireNonNull(packageName); + this.id = id; + } + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index ddf166eb0bd5..63952b086c1c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1904,7 +1904,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData fallback = new WallpaperData(wallpaper.userId, getWallpaperDir(wallpaper.userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); - ensureSaneWallpaperData(fallback, DEFAULT_DISPLAY); + ensureSaneWallpaperData(fallback); bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply); mWaitingForUnlock = true; } @@ -2425,7 +2425,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (cropHint == null) { cropHint = new Rect(0, 0, 0, 0); } else { - if (cropHint.isEmpty() + if (cropHint.width() < 0 || cropHint.height() < 0 || cropHint.left < 0 || cropHint.top < 0) { throw new IllegalArgumentException("Invalid crop rect supplied: " + cropHint); @@ -3077,7 +3077,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper = new WallpaperData(userId, getWallpaperDir(userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); mLockWallpaperMap.put(userId, wallpaper); - ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY); + ensureSaneWallpaperData(wallpaper); } else { // sanity fallback: we're in bad shape, but establishing a known // valid system+lock WallpaperData will keep us from dying. @@ -3085,7 +3085,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper = new WallpaperData(userId, getWallpaperDir(userId), WALLPAPER, WALLPAPER_CROP); mWallpaperMap.put(userId, wallpaper); - ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY); + ensureSaneWallpaperData(wallpaper); } } } @@ -3196,10 +3196,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY); - ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY); + ensureSaneWallpaperData(wallpaper); WallpaperData lockWallpaper = mLockWallpaperMap.get(userId); if (lockWallpaper != null) { - ensureSaneWallpaperData(lockWallpaper, DEFAULT_DISPLAY); + ensureSaneWallpaperData(lockWallpaper); } } @@ -3215,15 +3215,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void ensureSaneWallpaperData(WallpaperData wallpaper, int displayId) { - final DisplayData size = getDisplayDataOrCreate(displayId); - - if (displayId == DEFAULT_DISPLAY) { - // crop, if not previously specified - if (wallpaper.cropHint.width() <= 0 - || wallpaper.cropHint.height() <= 0) { - wallpaper.cropHint.set(0, 0, size.mWidth, size.mHeight); - } + private void ensureSaneWallpaperData(WallpaperData wallpaper) { + // Only overwrite cropHint if the rectangle is invalid. + if (wallpaper.cropHint.width() < 0 + || wallpaper.cropHint.height() < 0) { + wallpaper.cropHint.set(0, 0, 0, 0); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a66741c90b1c..ed9b01916657 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,7 +42,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; @@ -109,7 +108,6 @@ import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_UNSET; -import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -196,6 +194,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import static com.android.server.wm.TaskPersister.DEBUG; import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; @@ -5213,6 +5212,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateReportedVisibilityLocked(); } + void onStartingWindowDrawn() { + if (task != null) { + task.setHasBeenVisible(true); + } + } + /** Called when the windows associated app window container are drawn. */ void onWindowsDrawn(boolean drawn, long timestampNs) { mDrawn = drawn; @@ -5811,18 +5816,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @VisibleForTesting - boolean shouldAnimate(int transit) { - if (task != null && !task.shouldAnimate()) { - return false; - } - final boolean isSplitScreenPrimary = - getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - final boolean allowSplitScreenPrimaryAnimation = transit != TRANSIT_WALLPAPER_OPEN; - - // We animate always if it's not split screen primary, and only some special cases in split - // screen primary because it causes issues with stack clipping when we run an un-minimize - // animation at the same time. - return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation; + boolean shouldAnimate() { + return task == null || task.shouldAnimate(); } /** @@ -5918,7 +5913,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void prepareSurfaces() { - final boolean show = isVisible() || isAnimating(PARENTS); + final boolean show = isVisible() || isAnimatingExcluding(PARENTS, + ANIMATION_TYPE_SCREEN_ROTATION); if (mSurfaceControl != null) { if (show && !mLastSurfaceShowing) { @@ -6494,11 +6490,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect containingBounds = mTmpBounds; mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, orientation, orientationRequested, canChangeOrientation); - resolvedBounds.set(containingAppBounds); + resolvedBounds.set(containingBounds); // The size of floating task is fixed (only swap), so the aspect ratio is already correct. if (!mCompatDisplayInsets.mIsFloating) { applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds); } + // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in + // the container app bounds. Otherwise the entire container bounds are available. + final boolean fillContainer = resolvedBounds.equals(containingBounds); + if (!fillContainer) { + // The horizontal position should not cover insets. + resolvedBounds.left = containingAppBounds.left; + } // Use resolvedBounds to compute other override configurations such as appBounds. The bounds // are calculated in compat container space. The actual position on screen will be applied @@ -6567,7 +6570,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int offsetX = getHorizontalCenterOffset( (int) viewportW, (int) (contentW * mSizeCompatScale)); // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = parentAppBounds.left + offsetX; + final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX; final int screenPosY = parentBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { @@ -7365,7 +7368,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final ActivityStack stack = getRootTask(); return stack != null && - stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */); + stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, + stack.topRunningActivity() == this /* isTop */); } void setTurnScreenOn(boolean turnScreenOn) { @@ -7664,8 +7668,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Ensure the app bounds won't overlap with insets. Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]); } - // The horizontal position is centered and it should not cover insets. - outBounds.left = outAppBounds.left; } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index f5eba96a96d1..0f52248eb4dd 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -20,7 +20,6 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; @@ -685,9 +684,7 @@ class ActivityStack extends Task { return; } - setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - false /* creating */); + setWindowingMode(windowingMode, false /* creating */); } /** @@ -709,27 +706,22 @@ class ActivityStack extends Task { * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending * on the state of things. For example, WINDOWING_MODE_UNDEFINED will resolve to the * previous non-transient mode if this stack is currently in a transient mode. - * @param animate Can be used to prevent animation. - * @param showRecents Controls whether recents is shown on the other side of a split while - * entering split mode. - * @param enteringSplitScreenMode {@code true} if entering split mode. - * @param deferEnsuringVisibility Whether visibility updates are deferred. This is set when - * many operations (which can effect visibility) are being performed in bulk. * @param creating {@code true} if this is being run during ActivityStack construction. */ - void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents, - boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, boolean creating) { + void setWindowingMode(int preferredWindowingMode, boolean creating) { mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction( - preferredWindowingMode, animate, showRecents, enteringSplitScreenMode, - deferEnsuringVisibility, creating)); + preferredWindowingMode, creating)); } - private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, boolean animate, - boolean showRecents, boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, + private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, boolean creating) { + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + if (taskDisplayArea == null) { + Slog.d(TAG, "taskDisplayArea is null, bail early"); + return; + } final int currentMode = getWindowingMode(); final int currentOverrideMode = getRequestedOverrideWindowingMode(); - final TaskDisplayArea taskDisplayArea = getDisplayArea(); final Task topTask = getTopMostTask(); int windowingMode = preferredWindowingMode; if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED @@ -753,12 +745,9 @@ class ActivityStack extends Task { final boolean alreadyInSplitScreenMode = taskDisplayArea.isSplitScreenModeActivated(); - // Don't send non-resizeable notifications if the windowing mode changed was a side effect - // of us entering split-screen mode. - final boolean sendNonResizeableNotification = !enteringSplitScreenMode; // Take any required action due to us not supporting the preferred windowing mode. if (alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN - && sendNonResizeableNotification && isActivityTypeStandardOrUndefined()) { + && isActivityTypeStandardOrUndefined()) { final boolean preferredSplitScreen = preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -768,7 +757,7 @@ class ActivityStack extends Task { // warning toast about it. mAtmService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - taskDisplayArea.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(this); } } @@ -794,7 +783,7 @@ class ActivityStack extends Task { if (currentMode == WINDOWING_MODE_PINNED) { mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); } - if (sendNonResizeableNotification && likelyResolvedMode != WINDOWING_MODE_FULLSCREEN + if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN && topActivity != null && !topActivity.noDisplay && topActivity.isNonResizableOrForcedResizable(likelyResolvedMode)) { // Inform the user that they are starting an app that may not work correctly in @@ -806,7 +795,7 @@ class ActivityStack extends Task { mAtmService.deferWindowLayout(); try { - if (!animate && topActivity != null) { + if (topActivity != null) { mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); @@ -845,36 +834,11 @@ class ActivityStack extends Task { false /*preserveWindows*/, true /*deferResume*/); } } finally { - if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay() - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Make sure recents stack exist when creating a dock stack as it normally needs to - // be on the other side of the docked stack and we make visibility decisions based - // on that. - // TODO: This is only here to help out with the case where recents stack doesn't - // exist yet. For that case the initial size of the split-screen stack will be the - // the one where the home stack is visible since recents isn't visible yet, but the - // divider will be off. I think we should just make the initial bounds that of home - // so that the divider matches and remove this logic. - // TODO: This is currently only called when entering split-screen while in another - // task, and from the tests - // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode - final boolean isRecentsComponentHome = - mAtmService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser); - final ActivityStack recentStack = taskDisplayArea.getOrCreateStack( - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS, - true /* onTop */); - recentStack.moveToFront("setWindowingMode"); - // If task moved to docked stack - show recents if needed. - mWmService.showRecentApps(); - } mAtmService.continueWindowLayout(); } - if (!deferEnsuringVisibility) { - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - mRootWindowContainer.resumeFocusedStacksTopActivities(); - } + mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedStacksTopActivities(); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 02601ff4b6e3..9de8ef776b41 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -80,8 +80,6 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; -import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; -import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -140,7 +138,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; -import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -382,71 +379,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { private boolean mInitialized; - private final MoveTaskToFullscreenHelper mMoveTaskToFullscreenHelper = - new MoveTaskToFullscreenHelper(); - private class MoveTaskToFullscreenHelper { - private TaskDisplayArea mToDisplayArea; - private boolean mOnTop; - private Task mTopTask; - private boolean mSchedulePictureInPictureModeChange; - - void process(ActivityStack fromStack, TaskDisplayArea toDisplayArea, boolean onTop, - boolean schedulePictureInPictureModeChange) { - mSchedulePictureInPictureModeChange = schedulePictureInPictureModeChange; - mToDisplayArea = toDisplayArea; - mOnTop = onTop; - mTopTask = fromStack.getTopMostTask(); - - final PooledConsumer c = PooledLambda.obtainConsumer( - MoveTaskToFullscreenHelper::processLeafTask, this, PooledLambda.__(Task.class)); - fromStack.forAllLeafTasks(c, false /* traverseTopToBottom */); - c.recycle(); - mToDisplayArea = null; - mTopTask = null; - } - - private void processLeafTask(Task task) { - // This is a one level task that we don't need to create stack for reparenting to. - if (task.isRootTask() && DisplayContent.alwaysCreateStack(WINDOWING_MODE_FULLSCREEN, - task.getActivityType())) { - final ActivityStack stack = (ActivityStack) task; - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (mToDisplayArea.getDisplayId() != stack.getDisplayId()) { - stack.reparent(mToDisplayArea, mOnTop); - } else if (mOnTop) { - mToDisplayArea.positionStackAtTop(stack, false /* includingParents */); - } else { - mToDisplayArea.positionStackAtBottom(stack); - } - return; - } - - final ActivityStack toStack = mToDisplayArea.getOrCreateStack(null, mTmpOptions, task, - task.getActivityType(), mOnTop); - if (task == toStack) { - // The task was reused as the root task. - return; - } - - if (mOnTop) { - final boolean isTopTask = task == mTopTask; - // Defer resume until all the tasks have been moved to the fullscreen stack - task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /*animate*/, - DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - onTop"); - MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext, - task.effectiveUid, task.realActivity.flattenToString()); - } else { - // Position the tasks in the fullscreen stack in order at the bottom of the - // stack. Also defer resume until all the tasks have been moved to the - // fullscreen stack. - task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE, - !ANIMATE, DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - NOT_onTop"); - } - } - } - /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -1501,41 +1433,43 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mResizingTasksDuringAnimation.clear(); } - /** - * TODO: This should just change the windowing mode and resize vs. actually moving task around. - * Can do that once we are no longer using static stack ids. - */ - private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack, - int toDisplayId, boolean onTop) { + void setSplitScreenResizing(boolean resizing) { + if (resizing == mDockedStackResizing) { + return; + } - mService.deferWindowLayout(); - try { - final int windowingMode = fromStack.getWindowingMode(); - final TaskDisplayArea toDisplayArea = mRootWindowContainer - .getDisplayContent(toDisplayId).getDefaultTaskDisplayArea(); + mDockedStackResizing = resizing; + mWindowManager.setDockedStackResizing(resizing); + } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // We are moving all tasks from the docked stack to the fullscreen stack, - // which is dismissing the docked stack, so resize all other stacks to - // fullscreen here already so we don't end up with resize trashing. - for (int i = toDisplayArea.getStackCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = toDisplayArea.getStackAt(i); - if (!otherStack.inSplitScreenSecondaryWindowingMode()) { - continue; - } - otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - } - } + private void removePinnedStackInSurfaceTransaction(ActivityStack stack) { + /** + * Workaround: Force-stop all the activities in the pinned stack before we reparent them + * to the fullscreen stack. This is to guarantee that when we are removing a stack, + * that the client receives onStop() before it is reparented. We do this by detaching + * the stack from the display so that it will be considered invisible when + * ensureActivitiesVisible() is called, and all of its activities will be marked + * invisible as well and added to the stopping list. After which we process the + * stopping list by handling the idle. + */ + stack.cancelAnimation(); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); + stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); + activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, + true /* processPausingActivities */, null /* configuration */); - // If we are moving from the pinned stack, then the animation takes care of updating - // the picture-in-picture mode. - final boolean schedulePictureInPictureModeChange = - windowingMode == WINDOWING_MODE_PINNED; + // Reparent all the tasks to the bottom of the display + final DisplayContent toDisplay = + mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); - if (fromStack.hasChild()) { - mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); - mMoveTaskToFullscreenHelper.process( - fromStack, toDisplayArea, onTop, schedulePictureInPictureModeChange); + mService.deferWindowLayout(); + try { + stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); + if (toDisplay.getDisplayId() != stack.getDisplayId()) { + stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */); + } else { + toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack); } mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); @@ -1545,41 +1479,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } - void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) { - // TODO(b/153089193): Support moving within the same task display area - mWindowManager.inSurfaceTransaction(() -> - moveTasksToFullscreenStackInSurfaceTransaction(fromStack, DEFAULT_DISPLAY, onTop)); - } - - void setSplitScreenResizing(boolean resizing) { - if (resizing == mDockedStackResizing) { - return; - } - - mDockedStackResizing = resizing; - mWindowManager.setDockedStackResizing(resizing); - } - private void removeStackInSurfaceTransaction(ActivityStack stack) { if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) { - /** - * Workaround: Force-stop all the activities in the pinned stack before we reparent them - * to the fullscreen stack. This is to guarantee that when we are removing a stack, - * that the client receives onStop() before it is reparented. We do this by detaching - * the stack from the display so that it will be considered invisible when - * ensureActivitiesVisible() is called, and all of its activities will be marked - * invisible as well and added to the stopping list. After which we process the - * stopping list by handling the idle. - */ - stack.cancelAnimation(); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); - stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); - activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, - true /* processPausingActivities */, null /* configuration */); - - // Move all the tasks to the bottom of the fullscreen stack - moveTasksToFullscreenStackLocked(stack, !ON_TOP); + removePinnedStackInSurfaceTransaction(stack); } else { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class)); @@ -2296,7 +2198,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // split-screen in split-screen. mService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - taskDisplayArea.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(task.getStack()); taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, true /* notifyClients */); } diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index d777f3fee1a2..4e85837e9616 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; @@ -191,8 +192,9 @@ public class ActivityStartController { try { // TODO(multi-display-area): Support starting home in a task display area // Make sure home stack exist on display. + // TODO(b/153624902): Replace with TaskDisplayArea#getOrCreateRootHomeTask() homeStack = display.getDefaultTaskDisplayArea().getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, ON_TOP); } finally { mSupervisor.endDeferResume(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b1968765300..c253cd2c3297 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3324,33 +3324,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); return; } - // Place the task in the right stack if it isn't there already based on - // the requested bounds. - // The stack transition logic is: - // - a null bounds on a freeform task moves that task to fullscreen - // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves - // that task to freeform - // - otherwise the task is not moved - ActivityStack stack = task.getStack(); if (!task.getWindowConfiguration().canResizeTask()) { throw new IllegalArgumentException("resizeTask not allowed on task=" + task); } - if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP); - } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP); - } // Reparent the task to the right stack if necessary boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0; - if (stack != task.getStack()) { - // Defer resume until the task is resized below - task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, - DEFER_RESUME, "resizeTask"); - preserveWindow = false; - } // After reparenting (which only resizes the task to the stack bounds), resize the // task to the actual bounds provided @@ -4022,28 +4001,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @Override - // TODO: API should just be about changing windowing modes... - public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) { - enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, - "moveTasksToFullscreenStack()"); - synchronized (mGlobalLock) { - final long origId = Binder.clearCallingIdentity(); - try { - final ActivityStack stack = mRootWindowContainer.getStack(fromStackId); - if (stack != null){ - if (!stack.isActivityTypeStandardOrUndefined()) { - throw new IllegalArgumentException( - "You can't move tasks from non-standard stacks."); - } - mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - /** * Moves the top activity in the input stackId to the pinned stack. * diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f86aeb2244dc..6f1ddcd793a9 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -76,7 +76,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import android.annotation.DrawableRes; @@ -404,11 +403,18 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; } - boolean isNextAppTransitionOpenCrossProfileApps() { return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS; } + boolean isNextAppTransitionCustomFromRecents() { + final RecentTasks recentTasks = mService.mAtmService.getRecentTasks(); + final String recentsPackageName = + (recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null; + return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM + && mNextAppTransitionPackage.equals(recentsPackageName); + } + /** * @return true if and only if we are currently fetching app transition specs from the future * passed into {@link #overridePendingAppTransitionMultiThumbFuture} @@ -1807,15 +1813,11 @@ public class AppTransition implements Dump { } int getAppStackClipMode() { - // When dismiss keyguard animation occurs, clip before the animation to prevent docked - // app from showing beyond the divider - if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { - return STACK_CLIP_BEFORE_ANIM; - } return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER ? STACK_CLIP_NONE : STACK_CLIP_AFTER_ANIM; } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 90fdf19d9781..a512337c96ec 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -54,7 +54,6 @@ import java.util.function.Predicate; * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks. * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks. * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container. - * Cannot have a sibling that is of type ANY. * * @param <T> type of the children of the DisplayArea. */ @@ -253,6 +252,12 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { super.prepareSurfaces(); getBounds(mTmpDimBoundsRect); + // If SystemUI is dragging for recents, we want to reset the dim state so any dim layer + // on the display level fades out. + if (forAllTasks(task -> !task.canAffectSystemUiFlags())) { + mDimmer.resetDimStates(); + } + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { scheduleAnimation(); } @@ -268,7 +273,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { ANY; static void checkSiblings(Type bottom, Type top) { - checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY"); checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS), bottom + " must be above BELOW_TASKS"); checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS), diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 982157336295..15b6f9735bf1 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -16,9 +16,14 @@ package com.android.server.wm; +import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; + import android.content.res.Resources; import android.text.TextUtils; +import java.util.ArrayList; +import java.util.List; + /** * Policy that manages DisplayAreas. */ @@ -37,9 +42,9 @@ public abstract class DisplayAreaPolicy { protected final DisplayArea<? extends WindowContainer> mImeContainer; /** - * The Tasks container. Tasks etc. are automatically added to this container. + * The task display areas. Tasks etc. are automatically added to these containers. */ - protected final DisplayArea<? extends ActivityStack> mTaskContainers; + protected final List<TaskDisplayArea> mTaskDisplayAreas; /** * Construct a new {@link DisplayAreaPolicy} @@ -48,19 +53,19 @@ public abstract class DisplayAreaPolicy { * @param content the display content for which the policy applies * @param root the root display area under which the policy operates * @param imeContainer the ime container that the policy must attach - * @param taskDisplayArea the task container that the policy must attach + * @param taskDisplayAreas the task display areas that the policy must attach * * @see #attachDisplayAreas() */ protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, - DisplayArea<? extends ActivityStack> taskDisplayArea) { + List<TaskDisplayArea> taskDisplayAreas) { mWmService = wmService; mContent = content; mRoot = root; mImeContainer = imeContainer; - mTaskContainers = taskDisplayArea; + mTaskDisplayAreas = taskDisplayAreas; } /** @@ -80,15 +85,32 @@ public abstract class DisplayAreaPolicy { */ public abstract void addWindow(WindowToken token); + /** + * @return the number of task display areas on the display. + */ + public int getTaskDisplayAreaCount() { + return mTaskDisplayAreas.size(); + } + + /** + * @return the task display area at index. + */ + public TaskDisplayArea getTaskDisplayAreaAt(int index) { + return mTaskDisplayAreas.get(index); + } + /** Provider for platform-default display area policy. */ static final class DefaultProvider implements DisplayAreaPolicy.Provider { @Override public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, - TaskDisplayArea taskDisplayArea) { + DisplayArea<? extends WindowContainer> imeContainer) { + final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService, + "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER); + final List<TaskDisplayArea> tdaList = new ArrayList<>(); + tdaList.add(defaultTaskDisplayArea); return new DisplayAreaPolicyBuilder() - .build(wmService, content, root, imeContainer, taskDisplayArea); + .build(wmService, content, root, imeContainer, tdaList); } } @@ -107,8 +129,7 @@ public abstract class DisplayAreaPolicy { */ DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, - TaskDisplayArea taskDisplayArea); + DisplayArea<? extends WindowContainer> imeContainer); /** * Instantiate the device-specific {@link Provider}. diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index e8becfa27fac..2c2f43392eca 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -201,8 +201,8 @@ class DisplayAreaPolicyBuilder { Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, - DisplayArea<? extends ActivityStack> taskDisplayArea, ArrayList<Feature> features) { - super(wmService, content, root, imeContainer, taskDisplayArea); + List<TaskDisplayArea> taskDisplayAreas, ArrayList<Feature> features) { + super(wmService, content, root, imeContainer, taskDisplayAreas); mFeatures = features; mAreas = new HashMap<>(features.size()); for (int i = 0; i < mFeatures.size(); i++) { @@ -267,7 +267,7 @@ class DisplayAreaPolicyBuilder { areaForLayer[layer].mChildren.add(leafArea); leafType = type; if (leafType == LEAF_TYPE_TASK_CONTAINERS) { - leafArea.mExisting = mTaskContainers; + addTaskDisplayAreasToLayer(areaForLayer[layer], layer); } else if (leafType == LEAF_TYPE_IME_CONTAINERS) { leafArea.mExisting = mImeContainer; } @@ -278,6 +278,17 @@ class DisplayAreaPolicyBuilder { root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas); } + /** Adds all task display areas to the specified layer */ + private void addTaskDisplayAreasToLayer(PendingArea parentPendingArea, int layer) { + final int count = mTaskDisplayAreas.size(); + for (int i = 0; i < count; i++) { + PendingArea leafArea = new PendingArea(null, layer, parentPendingArea); + leafArea.mExisting = mTaskDisplayAreas.get(i); + leafArea.mMaxLayer = layer; + parentPendingArea.mChildren.add(leafArea); + } + } + @Override public void addWindow(WindowToken token) { DisplayArea.Tokens area = findAreaForToken(token); @@ -317,12 +328,16 @@ class DisplayAreaPolicyBuilder { return this; } + protected List<Feature> getFeatures() { + return mFeatures; + } + Result build(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, - DisplayArea<? extends ActivityStack> taskDisplayArea) { + List<TaskDisplayArea> taskDisplayAreas) { - return new Result(wmService, content, root, imeContainer, taskDisplayArea, new ArrayList<>( + return new Result(wmService, content, root, imeContainer, taskDisplayAreas, new ArrayList<>( mFeatures)); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7931cd58d977..80a1a4592ff3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -72,6 +72,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; +import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; @@ -269,8 +270,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo new NonAppWindowContainers("mOverlayContainers", mWmService); /** The containers below are the only child containers {@link #mWindowContainers} can have. */ - // Contains all window containers that are related to apps (Activities) - final TaskDisplayArea mTaskContainers = new TaskDisplayArea(this, mWmService); // Contains all IME window containers. Note that the z-ordering of the IME windows will depend // on the IME target. We mainly have this container grouping so we can keep track of all the IME @@ -972,7 +971,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.addChild(mOverlayContainers, null); mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate( - mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); + mWmService, this, mRootDisplayArea, mImeWindowsContainers); mWindowContainers.addChildren(); // Sets the display content for the children. @@ -1082,8 +1081,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return null; } mShellRoots.put(windowType, root); - SurfaceControl out = new SurfaceControl(); - out.copyFrom(rootLeash); + SurfaceControl out = new SurfaceControl(rootLeash); return out; } @@ -2071,13 +2069,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } protected int getTaskDisplayAreaCount() { - // TODO(multi-display-area): Report actual display area count - return 1; + return mDisplayAreaPolicy.getTaskDisplayAreaCount(); } protected TaskDisplayArea getTaskDisplayAreaAt(int index) { - // TODO(multi-display-area): Report actual display area values - return mTaskContainers; + return mDisplayAreaPolicy.getTaskDisplayAreaAt(index); } ActivityStack getStack(int rootTaskId) { @@ -2403,7 +2399,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents). */ TaskDisplayArea getDefaultTaskDisplayArea() { - return mTaskContainers; + return mDisplayAreaPolicy.getTaskDisplayAreaAt(0); } @Override @@ -3554,6 +3550,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo onWallpaper, goingToShade, subtle)); } }, true /* traverseTopToBottom */); + for (int i = mShellRoots.size() - 1; i >= 0; --i) { + mShellRoots.valueAt(i).startAnimation(policy.createHiddenByKeyguardExit( + onWallpaper, goingToShade, subtle)); + } } /** @return {@code true} if there is window to wait before enabling the screen. */ @@ -5433,6 +5433,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mDisplayPolicy.getSystemUiContext(); } + Point getDisplayPosition() { + return mWmService.mDisplayManagerInternal.getDisplayPosition(getDisplayId()); + } + class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index caaa173cb4c0..e9d3d56ee283 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -696,6 +696,10 @@ public class DisplayPolicy { mForceShowSystemBarsFromExternal = forceShowSystemBars; } + boolean getForceShowSystemBars() { + return mForceShowSystemBarsFromExternal; + } + public boolean hasNavigationBar() { return mHasNavigationBar; } @@ -1494,9 +1498,7 @@ public class DisplayPolicy { final int behavior = mLastBehavior; boolean navVisible = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL ? (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 - : mNavigationBar != null && mNavigationBar.getControllableInsetProvider() != null - && mNavigationBar.getControllableInsetProvider().isClientVisible() - && !mDisplayContent.getInsetsPolicy().isTransient(ITYPE_NAVIGATION_BAR); + : isNavigationBarRequestedVisible(); boolean navTranslucent = (sysui & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0; boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0 @@ -1533,6 +1535,14 @@ public class DisplayPolicy { mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation; } + boolean isNavigationBarRequestedVisible() { + final InsetsSourceProvider provider = + mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_NAVIGATION_BAR); + return provider == null + ? InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR) + : provider.isClientVisible(); + } + void updateHideNavInputEventReceiver(boolean navVisible, boolean navAllowedHidden) { // When the navigation bar isn't visible, we put up a fake input window to catch all // touch events. This way we can detect when the user presses anywhere to bring back the @@ -3879,10 +3889,10 @@ public class DisplayPolicy { WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; - lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + lp.setFitInsetsTypes(0); lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index e4e57168efe7..61a199a816df 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -99,7 +99,7 @@ class InsetsPolicy { } private void updateHideNavInputEventReceiver() { - mPolicy.updateHideNavInputEventReceiver(!isHidden(ITYPE_NAVIGATION_BAR), + mPolicy.updateHideNavInputEventReceiver(mPolicy.isNavigationBarRequestedVisible(), mFocusedWin != null && mFocusedWin.mAttrs.insetsFlags.behavior != BEHAVIOR_SHOW_BARS_BY_TOUCH); } @@ -304,7 +304,10 @@ class InsetsPolicy { // We need to force system bars when the docked stack is visible, when the freeform stack // is visible but also when we are resizing for the transitions when docked stack // visibility changes. - return isDockedStackVisible || isFreeformStackVisible || isResizing; + return isDockedStackVisible + || isFreeformStackVisible + || isResizing + || mPolicy.getForceShowSystemBars(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index a031fe82d48b..0a9878dd660b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -442,10 +442,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Always prepare an app transition since we rely on the transition callbacks to cleanup mWindowManager.prepareAppTransition(TRANSIT_NONE, false); controller.setCancelOnNextTransitionStart(); - } else { - // Just cancel directly to unleash from launcher when the next launching task is the - // current top task. - mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged"); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 6fda1170a3f5..54210ae1c0b0 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; @@ -99,6 +100,8 @@ public class RecentsAnimationController implements DeathRecipient { private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); + private final IntArray mPendingNewTaskTargets = new IntArray(0); + private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations = new ArrayList<>(); private final int mDisplayId; @@ -220,6 +223,10 @@ public class RecentsAnimationController implements DeathRecipient { if (mCanceled) { return; } + // Remove all new task targets. + for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { + removeTaskInternal(mPendingNewTaskTargets.get(i)); + } } // Note, the callback will handle its own synchronization, do not lock on WM lock @@ -310,6 +317,18 @@ public class RecentsAnimationController implements DeathRecipient { mWillFinishToHome = willFinishToHome; } } + + @Override + public boolean removeTask(int taskId) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + return removeTaskInternal(taskId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; /** @@ -405,11 +424,17 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { + return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */); + } + + @VisibleForTesting + AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, + OnAnimationFinishedCallback finishedCallback) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, isRecentTaskInvisible); task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */, - ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_RECENTS, finishedCallback); task.commitPendingTransaction(); mPendingAnimations.add(taskAdapter); return taskAdapter; @@ -489,6 +514,49 @@ public class RecentsAnimationController implements DeathRecipient { } } + void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) { + if (mRunner != null) { + final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback); + if (target == null) return; + + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); + try { + mRunner.onTaskAppeared(target); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); + } + } + } + + private RemoteAnimationTarget createTaskRemoteAnimation(Task task, + OnAnimationFinishedCallback finishedCallback) { + final SparseBooleanArray recentTaskIds = + mService.mAtmService.getRecentTasks().getRecentTaskIds(); + TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, + !recentTaskIds.get(task.mTaskId), finishedCallback); + mPendingNewTaskTargets.add(task.mTaskId); + return adapter.createRemoteAnimationTarget(); + } + + private boolean removeTaskInternal(int taskId) { + boolean result = false; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + // Only allows when task target has became visible to user, to prevent + // the flickering during remove animation and task visible. + final TaskAnimationAdapter target = mPendingAnimations.get(i); + if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) { + removeAnimation(target); + final int taskIndex = mPendingNewTaskTargets.indexOf(taskId); + if (taskIndex != -1) { + mPendingNewTaskTargets.remove(taskIndex); + } + result = true; + break; + } + } + return result; + } + private RemoteAnimationTarget[] createAppAnimations() { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index e1ef76f128cd..26d6c78b5d33 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -38,6 +37,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -66,6 +66,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT; import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER; import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES; @@ -178,7 +179,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private Object mLastWindowFreezeSource = null; private Session mHoldScreen = null; - private float mScreenBrightness = -1; + private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; private long mUserActivityTimeout = -1; private boolean mUpdateRotation = false; // Following variables are for debugging screen wakelock only. @@ -664,7 +665,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void setSecureSurfaceState(int userId, boolean disabled) { forAllWindows((w) -> { - if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) { + if (w.mHasSurface && userId == w.mShowUserId) { w.mWinAnimator.setSecureLocked(disabled); } }, true /* traverseTopToBottom */); @@ -826,7 +827,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } mHoldScreen = null; - mScreenBrightness = -1; + mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; mUserActivityTimeout = -1; mObscureApplicationContentOnSecondaryDisplays = false; mSustainedPerformanceModeCurrent = false; @@ -936,12 +937,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mWmService.setHoldScreenLocked(mHoldScreen); if (!mWmService.mDisplayFrozen) { - final int brightness = mScreenBrightness < 0 || mScreenBrightness > 1.0f - ? -1 : toBrightnessOverride(mScreenBrightness); - + final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN + || mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX + ? PowerManager.BRIGHTNESS_INVALID_FLOAT : mScreenBrightnessOverride; + int brightnessFloatAsIntBits = Float.floatToIntBits(brightnessOverride); // Post these on a handler such that we don't call into power manager service while // holding the window manager lock to avoid lock contention with power manager lock. - mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightness, 0).sendToTarget(); + mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightnessFloatAsIntBits, + 0).sendToTarget(); mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget(); } @@ -1125,8 +1128,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> + "has FLAG_KEEP_SCREEN_ON!!! called by%s", w, Debug.getCallers(10)); } - if (!syswin && w.mAttrs.screenBrightness >= 0 && mScreenBrightness < 0) { - mScreenBrightness = w.mAttrs.screenBrightness; + if (!syswin && w.mAttrs.screenBrightness >= 0 + && Float.isNaN(mScreenBrightnessOverride)) { + mScreenBrightnessOverride = w.mAttrs.screenBrightness; } final int type = attrs.type; @@ -1190,10 +1194,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return doRequest; } - private static int toBrightnessOverride(float value) { - return (int)(value * PowerManager.BRIGHTNESS_ON); - } - private final class MyHandler extends Handler { public MyHandler(Looper looper) { @@ -1205,7 +1205,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> switch (msg.what) { case SET_SCREEN_BRIGHTNESS_OVERRIDE: mWmService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager( - msg.arg1); + Float.intBitsToFloat(msg.arg1)); break; case SET_USER_ACTIVITY_TIMEOUT: mWmService.mPowerManagerInternal. @@ -1370,8 +1370,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> calculateDefaultMinimalSizeOfResizeableTasks(); final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea(); - defaultTaskDisplayArea.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, - ON_TOP); + defaultTaskDisplayArea.getOrCreateRootHomeTask(); positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent, false /* includingParents */); } @@ -1516,6 +1515,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Updates the extra information of the intent. if (fromHomeKey) { homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true); + mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity"); } // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. @@ -2119,16 +2119,19 @@ class RootWindowContainer extends WindowContainer<DisplayContent> try { final Task task = r.getTask(); - final ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask(); + // This will change the pinned stack's windowing mode to its original mode, ensuring // we only have one stack that is in pinned mode. if (pinnedStack != null) { pinnedStack.dismissPip(); } - final boolean singleActivity = task.getChildCount() == 1; + // Set a transition to ensure that we don't immediately try and update the visibility + // of the activity entering PIP + r.getDisplayContent().prepareAppTransition(TRANSIT_NONE, false); + final boolean singleActivity = task.getChildCount() == 1; final ActivityStack stack; if (singleActivity) { stack = r.getRootTask(); @@ -2151,11 +2154,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.continueWindowLayout(); } - // TODO: revisit the following statement after the animation is moved from WM to SysUI. - // Update the visibility of all activities after the they have been reparented to the new - // stack. This MUST run after the animation above is scheduled to ensure that the windows - // drawn signal is scheduled after the bounds animation start call on the bounds animator - // thread. ensureActivitiesVisible(null, 0, false /* preserveWindows */); resumeFocusedStacksTopActivities(); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 5f3732a58824..56147f216e73 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -51,6 +51,7 @@ import android.view.IWindowId; import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.InputChannel; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -158,10 +159,22 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { + return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, + outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, + outInsetsState, outActiveControls, UserHandle.getUserId(mUid)); + } + + + @Override + public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, int userId, Rect outFrame, + Rect outContentInsets, Rect outStableInsets, + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, - outInsetsState); + outInsetsState, outActiveControls, userId); } @Override @@ -171,7 +184,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, new Rect() /* outFrame */, outContentInsets, outStableInsets, new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */, - outInsetsState); + outInsetsState, null, UserHandle.getUserId(mUid)); } @Override @@ -191,7 +204,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -199,8 +213,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outContentInsets, outVisibleInsets, outStableInsets, outBackdropFrame, cutout, - mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize, - outBLASTSurfaceControl); + mergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls, + outSurfaceSize, outBLASTSurfaceControl); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index 9732637fdd4d..701feff8c6be 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -16,12 +16,20 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; + +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; + import android.annotation.NonNull; +import android.graphics.Point; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import android.view.DisplayInfo; import android.view.IWindow; import android.view.SurfaceControl; +import android.view.animation.Animation; /** * Represents a piece of the hierarchy under which a client Shell can manage sub-windows. @@ -70,5 +78,29 @@ public class ShellRoot { IWindow getClient() { return mClient; } + + void startAnimation(Animation anim) { + // Only do this for the divider + if (mToken.windowType != TYPE_DOCK_DIVIDER) { + return; + } + + DisplayInfo displayInfo = mToken.getFixedRotationTransformDisplayInfo(); + if (displayInfo == null) { + displayInfo = mDisplayContent.getDisplayInfo(); + } + + // Mostly copied from WindowState to enable keyguard transition animation + anim.initialize(displayInfo.logicalWidth, displayInfo.logicalHeight, + displayInfo.appWidth, displayInfo.appHeight); + anim.restrictDuration(MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mDisplayContent.mWmService.getWindowAnimationScaleLocked()); + final AnimationAdapter adapter = new LocalAnimationAdapter( + new WindowAnimationSpec(anim, new Point(0, 0), false /* canSkipFirstFrame */, + 0 /* windowCornerRadius */), + mDisplayContent.mWmService.mSurfaceAnimationRunner); + mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */, + ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */); + } } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 6358e4719fde..18e32c0683d6 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -202,6 +202,11 @@ class SurfaceAnimator { return mAnimation != null; } + @AnimationType + int getAnimationType() { + return mAnimationType; + } + /** * @return The current animation spec if we are running an animation, or {@code null} otherwise. */ @@ -453,32 +458,38 @@ class SurfaceAnimator { * Animation for screen rotation. * @hide */ - static final int ANIMATION_TYPE_SCREEN_ROTATION = 2; + static final int ANIMATION_TYPE_SCREEN_ROTATION = 1 << 1; /** * Animation for dimming. * @hide */ - static final int ANIMATION_TYPE_DIMMER = 3; + static final int ANIMATION_TYPE_DIMMER = 1 << 2; /** * Animation for recent apps. * @hide */ - static final int ANIMATION_TYPE_RECENTS = 4; + static final int ANIMATION_TYPE_RECENTS = 1 << 3; /** * Animation for a {@link WindowState} without animating the activity. * @hide */ - static final int ANIMATION_TYPE_WINDOW_ANIMATION = 5; + static final int ANIMATION_TYPE_WINDOW_ANIMATION = 1 << 4; /** * Animation to control insets. This is actually not an animation, but is used to give the * client a leash over the system window causing insets. * @hide */ - static final int ANIMATION_TYPE_INSETS_CONTROL = 6; + static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5; + + /** + * Bitmask to include all animation types. This is NOT an {@link AnimationType} + * @hide + */ + static final int ANIMATION_TYPE_ALL = -1; /** * The type of the animation. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index c891c1166a06..fd32724d82a3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -86,6 +86,8 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; +import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; @@ -135,6 +137,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -2334,32 +2337,30 @@ class Task extends WindowContainer<WindowContainer> { return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); } - private void resolveOrganizedOverrideConfiguration(Configuration newParentConfig) { + @Override + void resolveOverrideConfiguration(Configuration newParentConfig) { + mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); super.resolveOverrideConfiguration(newParentConfig); - if (!isOrganized()) { - return; - } - final Task root = getRootTask(); - if (root == this) { - return; + // Resolve override windowing mode to fullscreen for home task (even on freeform + // display), or split-screen-secondary if in split-screen mode. + int windowingMode = + getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = inSplitScreenWindowingMode() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + : WINDOWING_MODE_FULLSCREEN; + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); } - // Ensure to have the same windowing mode for the child tasks that controlled by task org. - getResolvedOverrideConfiguration().windowConfiguration - .setWindowingMode(root.getWindowingMode()); - } - - @Override - void resolveOverrideConfiguration(Configuration newParentConfig) { - if (!isLeafTask() || mCreatedByOrganizer) { - resolveOrganizedOverrideConfiguration(newParentConfig); + if (!isLeafTask()) { + // Compute configuration overrides for tasks that created by organizer, so that + // organizer can get the correct configuration from those tasks. + if (mCreatedByOrganizer) { + computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + } return; } - mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); - resolveOrganizedOverrideConfiguration(newParentConfig); - int windowingMode = - getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); } @@ -2448,7 +2449,8 @@ class Task extends WindowContainer<WindowContainer> { Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; - final Rect bounds = isOrganized() && !isRootTask() ? null : getLaunchBounds(); + final Task rootTask = getRootTask(); + final Rect bounds = rootTask != this && rootTask.isOrganized() ? null : getLaunchBounds(); setBounds(bounds); if (bounds != null && !bounds.isEmpty()) { // TODO: Review if we actually want to do this - we are setting the launch bounds @@ -3347,6 +3349,21 @@ class Task extends WindowContainer<WindowContainer> { @Override Dimmer getDimmer() { + // If the window is in multi-window mode, we want to dim at the Task level to ensure the dim + // bounds match the area the app lives in + if (inMultiWindowMode()) { + return mDimmer; + } + + // If we're not at the root task level, we want to keep traversing through the parents to + // find the root. + // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}. + // If true, we want to get the Dimmer from the level above since we don't want to animate + // the dim with the Task. + if (!isRootTask() || isTranslucent(null)) { + return super.getDimmer(); + } + return mDimmer; } @@ -3388,6 +3405,24 @@ class Task extends WindowContainer<WindowContainer> { } @Override + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final RecentsAnimationController control = mWmService.getRecentsAnimationController(); + if (control != null && enter + && getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) { + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, + "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + control, asTask(), AppTransition.appTransitionToString(transit)); + // We let the transition to be controlled by RecentsAnimation, and callback task's + // RemoteAnimationTarget for remote runner to animate. + control.addTaskToTargets(getRootTask(), finishedCallback); + } else { + super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); + } + } + + @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); final String doublePrefix = prefix + " "; @@ -4084,8 +4119,18 @@ class Task extends WindowContainer<WindowContainer> { } void setHasBeenVisible(boolean hasBeenVisible) { + final boolean prevHasBeenVisible = mHasBeenVisible; mHasBeenVisible = hasBeenVisible; if (hasBeenVisible) { + // If the task is not yet visible when it is added to the task organizer, then we should + // hide it to allow the task organizer to show it when it is properly reparented. We + // skip this for tasks created by the organizer because they can synchronously update + // the leash before new children are added to the task. + if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) { + getPendingTransaction().hide(getSurfaceControl()); + commitPendingTransaction(); + } + sendTaskAppeared(); if (!isRootTask()) { getRootTask().setHasBeenVisible(true); @@ -4105,20 +4150,18 @@ class Task extends WindowContainer<WindowContainer> { * Any time any of these conditions are updated, the updating code should call * sendTaskAppeared. */ - private boolean taskAppearedReady() { + boolean taskAppearedReady() { return mSurfaceControl != null && mTaskOrganizer != null && getHasBeenVisible(); } private void sendTaskAppeared() { - if (taskAppearedReady() && !mTaskAppearedSent) { - mTaskAppearedSent = true; + if (mTaskOrganizer != null) { mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this); } } private void sendTaskVanished() { - if (mTaskOrganizer != null && mTaskAppearedSent) { - mTaskAppearedSent = false; + if (mTaskOrganizer != null) { mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this); } } @@ -4132,15 +4175,6 @@ class Task extends WindowContainer<WindowContainer> { sendTaskVanished(); mTaskOrganizer = organizer; - // If the task is not yet visible when it is added to the task organizer, then we should - // hide it to allow the task organizer to show it when it is properly reparented. We skip - // this for tasks created by the organizer because they can synchronously update the leash - // before new children are added to the task. - if (!mCreatedByOrganizer && organizer != null - && (!getHasBeenVisible() || !hasVisibleChildren())) { - getPendingTransaction().hide(getSurfaceControl()); - commitPendingTransaction(); - } sendTaskAppeared(); onTaskOrganizerChanged(); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 8e45dc306b58..735bef87c94b 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -31,7 +31,6 @@ import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; @@ -141,8 +140,9 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { */ private boolean mRemoved; - TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) { - super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER); + TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name, + int displayAreaFeature) { + super(service, Type.ANY, name, displayAreaFeature); mDisplayContent = displayContent; mRootWindowContainer = service.mRoot; mAtmService = service.mAtmService; @@ -762,7 +762,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { */ ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop, Intent intent, Task candidateTask) { - if (!alwaysCreateStack(windowingMode, activityType)) { + // Need to pass in a determined windowing mode to see if a new stack should be created, + // so use its parent's windowing mode if it is undefined. + if (!alwaysCreateStack( + windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : getWindowingMode(), + activityType)) { ActivityStack stack = getStack(windowingMode, activityType); if (stack != null) { return stack; @@ -932,9 +936,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } else { addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); - stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - true /* creating */); + stack.setWindowingMode(windowingMode, true /* creating */); } return stack; } @@ -1144,7 +1146,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack stack = getStackAt(i); // Collect the root tasks that are currently being organized. - if (stack.isOrganized()) { + if (stack.mCreatedByOrganizer) { for (int k = stack.getChildCount() - 1; k >= 0; --k) { final ActivityStack childStack = (ActivityStack) stack.getChildAt(k); if (childStack.getActivityType() == activityType) { @@ -1163,17 +1165,23 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } void onSplitScreenModeDismissed() { + onSplitScreenModeDismissed(null /* toTop */); + } + + void onSplitScreenModeDismissed(ActivityStack toTop) { mAtmService.deferWindowLayout(); try { mLaunchRootTask = null; moveSplitScreenTasksToFullScreen(); } finally { - final ActivityStack topFullscreenStack = - getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); + final ActivityStack topFullscreenStack = toTop != null + ? toTop : getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); final ActivityStack homeStack = getOrCreateRootHomeTask(); - if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) { + if (homeStack != null && ((topFullscreenStack != null && !isTopStack(homeStack)) + || toTop != null)) { // Whenever split-screen is dismissed we want the home stack directly behind the // current top fullscreen stack so it shows up when the top stack is finished. + // Or, if the caller specified a stack to be on top after split-screen is dismissed. // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch // once we have that. @@ -1413,8 +1421,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { ActivityStack homeTask = getRootHomeTask(); if (homeTask == null && mDisplayContent.supportsSystemDecorations() && !mDisplayContent.isUntrustedVirtualDisplay()) { - homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, - false /* onTop */); + homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, false /* onTop */); } return homeTask; } @@ -1610,6 +1617,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } + @Override + boolean canCreateRemoteAnimationTarget() { + return true; + } + /** * Callback for when the order of the stacks in the display changes. */ diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 75f2e1f0a8ba..9873031e0138 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -131,6 +131,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) { + if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) { + // Skip if the task has not yet received taskAppeared(), except for tasks created + // by the organizer that don't receive that signal + return; + } mDeferTaskOrgCallbacksConsumer.accept(() -> { if (!task.isOrganized()) { // This is safe to ignore if the task is no longer organized @@ -145,6 +150,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void onBackPressedOnTaskRoot(Task task) { + if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) { + // Skip if the task has not yet received taskAppeared(), except for tasks created + // by the organizer that don't receive that signal + return; + } mDeferTaskOrgCallbacksConsumer.accept(() -> { if (!task.isOrganized()) { // This is safe to ignore if the task is no longer organized @@ -187,13 +197,23 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void addTask(Task t) { - mOrganizedTasks.add(t); - mOrganizer.onTaskAppeared(t); + if (t.mTaskAppearedSent) return; + + if (!mOrganizedTasks.contains(t)) { + mOrganizedTasks.add(t); + } + if (t.taskAppearedReady()) { + t.mTaskAppearedSent = true; + mOrganizer.onTaskAppeared(t); + } } void removeTask(Task t) { + if (t.mTaskAppearedSent) { + t.mTaskAppearedSent = false; + mOrganizer.onTaskVanished(t); + } mOrganizedTasks.remove(t); - mOrganizer.onTaskVanished(t); } void dispose() { @@ -442,9 +462,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mTmpTaskInfo = null; if (task.isOrganized()) { + // Because we defer sending taskAppeared() until the app has drawn, we may receive a + // configuration change before the state actually has the task registered. As such we + // should ignore these change events to the organizer until taskAppeared(). If the task + // was created by the organizer, then we always send the info change. final TaskOrganizerState state = mTaskOrganizerStates.get( task.mTaskOrganizer.asBinder()); - state.mOrganizer.onTaskInfoChanged(task, newInfo); + if (state != null) { + state.mOrganizer.onTaskInfoChanged(task, newInfo); + } } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index eb005e0f7eda..14e5c6cbf28d 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -68,6 +68,7 @@ import android.util.MergedConfiguration; import android.util.Slog; import android.view.DisplayCutout; import android.view.IWindowSession; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; @@ -164,6 +165,7 @@ class TaskSnapshotSurface implements StartingSurface { final Rect tmpContentInsets = new Rect(); final Rect tmpStableInsets = new Rect(); final InsetsState mTmpInsetsState = new InsetsState(); + final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); final TaskDescription taskDescription = new TaskDescription(); taskDescription.setBackgroundColor(WHITE); @@ -231,8 +233,8 @@ class TaskSnapshotSurface implements StartingSurface { } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, - View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect, tmpRect, - tmpCutout, null, mTmpInsetsState); + View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect, + tmpRect, tmpCutout, null, mTmpInsetsState, mTempControls); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); return null; @@ -249,7 +251,7 @@ class TaskSnapshotSurface implements StartingSurface { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, - sTmpSurfaceSize, sTmpSurfaceControl); + mTempControls, sTmpSurfaceSize, sTmpSurfaceControl); } catch (RemoteException e) { // Local call. } @@ -377,6 +379,7 @@ class TaskSnapshotSurface implements StartingSurface { frame = null; mTmpDstFrame.set(mFrame); } + mTmpDstFrame.offsetTo(0, 0); // Scale the mismatch dimensions to fill the task bounds mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 037b9c830a50..fba22dd4e9df 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -36,6 +36,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; @@ -790,7 +791,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * By default this predicate only checks if this container itself is actually running an * animation, but you can extend the check target over its relatives, or relax the condition * so that this can return {@code true} if an animation starts soon by giving a combination - * of {@link #AnimationFlags}. + * of {@link AnimationFlags}. * * Note that you can give a combination of bitmask flags to specify targets and condition for * checking animating status. @@ -800,12 +801,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * * Note that TRANSITION propagates to parents and children as well. * - * {@see AnimationFlags#TRANSITION} - * {@see AnimationFlags#PARENTS} - * {@see AnimationFlags#CHILDREN} + * @param flags The combination of bitmask flags to specify targets and condition for + * checking animating status. + * @param typesToCheck The combination of bitmask {@link AnimationType} to compare when + * determining if animating. + * + * @see AnimationFlags#TRANSITION + * @see AnimationFlags#PARENTS + * @see AnimationFlags#CHILDREN */ - boolean isAnimating(int flags) { - if (mSurfaceAnimator.isAnimating()) { + boolean isAnimating(int flags, int typesToCheck) { + int animationType = mSurfaceAnimator.getAnimationType(); + if (mSurfaceAnimator.isAnimating() && (animationType & typesToCheck) > 0) { return true; } if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) { @@ -813,14 +820,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } if ((flags & PARENTS) != 0) { final WindowContainer parent = getParent(); - if (parent != null && parent.isAnimating(flags & ~CHILDREN)) { + if (parent != null && parent.isAnimating(flags & ~CHILDREN, typesToCheck)) { return true; } } if ((flags & CHILDREN) != 0) { for (int i = 0; i < mChildren.size(); ++i) { final WindowContainer wc = mChildren.get(i); - if (wc.isAnimating(flags & ~PARENTS)) { + if (wc.isAnimating(flags & ~PARENTS, typesToCheck)) { return true; } } @@ -829,6 +836,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Similar to {@link #isAnimating(int, int)} except provide a bitmask of + * {@link AnimationType} to exclude, rather than include + * @param flags The combination of bitmask flags to specify targets and condition for + * checking animating status. + * @param typesToExclude The combination of bitmask {@link AnimationType} to exclude when + * checking if animating. + */ + boolean isAnimatingExcluding(int flags, int typesToExclude) { + return isAnimating(flags, ANIMATION_TYPE_ALL & ~typesToExclude); + } + + /** + * @see #isAnimating(int, int) + * TODO (b/152333373): Migrate calls to use isAnimating with specified animation type + */ + boolean isAnimating(int flags) { + return isAnimating(flags, ANIMATION_TYPE_ALL); + } + + /** * @return {@code true} when the container is waiting the app transition start, {@code false} * otherwise. */ @@ -2049,8 +2076,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see #getAnimationAdapter */ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) { if (mWmService.mDisableTransitionAnimation) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: transition animation is disabled or skipped. " @@ -2065,22 +2091,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation"); if (okToAnimate()) { - final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, - transit, enter, isVoiceInteraction); - AnimationAdapter adapter = adapters.first; - AnimationAdapter thumbnailAdapter = adapters.second; - if (adapter != null) { - startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback); - if (adapter.getShowWallpaper()) { - getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } - if (thumbnailAdapter != null) { - mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), - thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, - (type, anim) -> { }); - } - } + applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); } else { cancelAnimation(); } @@ -2174,6 +2185,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return resultAdapters; } + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, + transit, enter, isVoiceInteraction); + AnimationAdapter adapter = adapters.first; + AnimationAdapter thumbnailAdapter = adapters.second; + if (adapter != null) { + startAnimation(getPendingTransaction(), adapter, !isVisible(), + ANIMATION_TYPE_APP_TRANSITION, finishedCallback); + if (adapter.getShowWallpaper()) { + getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + } + if (thumbnailAdapter != null) { + mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), + thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); + } + } + } + final SurfaceAnimationRunner getSurfaceAnimationRunner() { return mWmService.mSurfaceAnimationRunner; } @@ -2273,6 +2304,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { mLastLayer = -1; reassignLayer(t); + + // Leash is now responsible for position, so set our position to 0. + t.setPosition(mSurfaceControl, 0, 0); + mLastSurfacePosition.set(0, 0); } @Override @@ -2280,6 +2315,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLastLayer = -1; mSurfaceFreezer.unfreeze(t); reassignLayer(t); + updateSurfacePosition(t); } /** @@ -2343,11 +2379,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - void updateSurfacePosition() { + final void updateSurfacePosition() { + updateSurfacePosition(getPendingTransaction()); + } + + void updateSurfacePosition(Transaction t) { // Avoid fighting with the organizer over Surface position. if (isOrganized()) return; - if (mSurfaceControl == null) { + if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) { return; } @@ -2356,7 +2396,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); } @@ -2479,9 +2519,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // We need to copy the SurfaceControl instead of returning the original // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls // to release themselves. - SurfaceControl sc = new SurfaceControl(); - sc.copyFrom(wc.getSurfaceControl()); - return sc; + return new SurfaceControl(wc.getSurfaceControl()); } WindowContainerToken toWindowContainerToken() { @@ -2548,4 +2586,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return willSync; } + + boolean useBLASTSync() { + return mUsingBLASTSyncTransaction; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3cbb85b55a4c..9d879765f8df 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; @@ -32,6 +33,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.content.pm.PackageManager.FEATURE_PC; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -72,6 +74,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; +import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; @@ -227,6 +230,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputWindowHandle; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; @@ -1356,7 +1360,8 @@ public class WindowManagerService extends IWindowManager.Stub LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, + int requestUserId) { int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1425,6 +1430,20 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_INVALID_DISPLAY; } + int userId = UserHandle.getUserId(session.mUid); + if (requestUserId != userId) { + try { + mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId, + false /*allowAll*/, ALLOW_NON_FULL, null, null); + } catch (Exception exp) { + ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d", + requestUserId); + return WindowManagerGlobal.ADD_INVALID_USER; + } + // It's fine to use this userId + userId = requestUserId; + } + ActivityRecord activity = null; final boolean hasParent = parentWindow != null; // Use existing parent window token for child windows since they go in the same token @@ -1513,7 +1532,7 @@ public class WindowManagerService extends IWindowManager.Stub } final WindowState win = new WindowState(this, session, client, token, parentWindow, - appOp[0], seq, attrs, viewVisibility, session.mUid, + appOp[0], seq, attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to @@ -1643,8 +1662,7 @@ public class WindowManagerService extends IWindowManager.Stub outStableInsets, outDisplayCutout)) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; } - outInsetsState.set(win.getInsetsState(), - win.mClient instanceof IWindow.Stub /* copySource */); + outInsetsState.set(win.getInsetsState(), win.isClientLocal()); if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; @@ -1678,6 +1696,8 @@ public class WindowManagerService extends IWindowManager.Stub } displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); + getInsetsSourceControls(win, outActiveControls); + ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); @@ -1832,8 +1852,7 @@ public class WindowManagerService extends IWindowManager.Stub if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { return true; } - if (DevicePolicyCache.getInstance().getScreenCaptureDisabled( - UserHandle.getUserId(w.mOwnerUid))) { + if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(w.mShowUserId)) { return true; } return false; @@ -2080,7 +2099,8 @@ public class WindowManagerService extends IWindowManager.Stub Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { int result = 0; boolean configChanged; final int pid = Binder.getCallingPid(); @@ -2106,6 +2126,10 @@ public class WindowManagerService extends IWindowManager.Stub win.finishSeamlessRotation(false /* timeout */); } + if (win.useBLASTSync()) { + result |= RELAYOUT_RES_BLAST_SYNC; + } + int attrChanges = 0; int flagChanges = 0; int privateFlagChanges = 0; @@ -2375,8 +2399,8 @@ public class WindowManagerService extends IWindowManager.Stub outStableInsets); outCutout.set(win.getWmDisplayCutout().getDisplayCutout()); outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw())); - outInsetsState.set(win.getInsetsState(), - win.mClient instanceof IWindow.Stub /* copySource */); + outInsetsState.set(win.getInsetsState(), win.isClientLocal()); + getInsetsSourceControls(win, outActiveControls); if (DEBUG) { Slog.v(TAG_WM, "Relayout given client " + client.asBinder() + ", requestedWidth=" + requestedWidth @@ -2413,6 +2437,21 @@ public class WindowManagerService extends IWindowManager.Stub return result; } + private void getInsetsSourceControls(WindowState win, InsetsSourceControl[] outControls) { + if (outControls != null) { + final InsetsSourceControl[] controls = + win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win); + Arrays.fill(outControls, null); + if (controls != null) { + final int length = Math.min(controls.length, outControls.length); + for (int i = 0; i < length; i++) { + outControls[i] = win.isClientLocal() + ? new InsetsSourceControl(controls[i]) : controls[i]; + } + } + } + } + private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator, boolean focusMayChange) { // Try starting an animation; if there isn't one, we @@ -2555,6 +2594,8 @@ public class WindowManagerService extends IWindowManager.Stub String packageName, boolean fromClientToken) { final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()"); + // WindowContext users usually don't hold MANAGE_APP_TOKEN permission. Check permissions + // by checkAddPermission. if (!callerCanManageAppTokens) { final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */, packageName, new int[1]); @@ -2569,7 +2610,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { if (!callerCanManageAppTokens) { if (packageName == null || !unprivilegedAppCanCreateTokenWith( - null /* parentWindow */, callingUid, type, type, null /* tokenForLog */, + null /* parentWindow */, callingUid, type, type, binder, packageName)) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } @@ -2594,7 +2635,7 @@ public class WindowManagerService extends IWindowManager.Stub new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens); } else { new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens, - false /* roundedCornerOverlay */, fromClientToken); + callingUid, false /* roundedCornerOverlay */, fromClientToken); } } } finally { @@ -2617,8 +2658,25 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void removeWindowToken(IBinder binder, int displayId) { - if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + final boolean callerCanManageAppTokens = + checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()"); + final WindowToken windowToken; + synchronized (mGlobalLock) { + windowToken = mRoot.getWindowToken(binder); + } + if (windowToken == null) { + ProtoLog.w(WM_ERROR, + "removeWindowToken: Attempted to remove non-existing token: %s", binder); + return; + } + final int callingUid = Binder.getCallingUid(); + + // If MANAGE_APP_TOKEN permission is not held(usually from WindowContext), callers can only + // remove the window tokens which they added themselves. + if (!callerCanManageAppTokens && (windowToken.getOwnerUid() == INVALID_UID + || callingUid != windowToken.getOwnerUid())) { + throw new SecurityException("removeWindowToken: Requires MANAGE_APP_TOKENS permission" + + " to remove token owned by another uid"); } final long origId = Binder.clearCallingIdentity(); @@ -2631,14 +2689,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } - final WindowToken token = dc.removeWindowToken(binder); - if (token == null) { - ProtoLog.w(WM_ERROR, - "removeWindowToken: Attempted to remove non-existing token: %s", - binder); - return; - } - + dc.removeWindowToken(binder); dc.getInputMonitor().updateInputWindowsLw(true /*force*/); } } finally { @@ -5317,7 +5368,8 @@ public class WindowManagerService extends IWindowManager.Stub throw new IllegalArgumentException( "Requested window " + client + " does not exist"); } - ProtoLog.w(WM_ERROR, "Failed looking up window callers=%s", Debug.getCallers(3)); + ProtoLog.w(WM_ERROR, "Failed looking up window session=%s callers=%s", session, + Debug.getCallers(3)); return null; } if (session != null && win.mSession != session) { @@ -5325,7 +5377,8 @@ public class WindowManagerService extends IWindowManager.Stub throw new IllegalArgumentException("Requested window " + client + " is in session " + win.mSession + ", not " + session); } - ProtoLog.w(WM_ERROR, "Failed looking up window callers=%s", Debug.getCallers(3)); + ProtoLog.w(WM_ERROR, "Failed looking up window session=%s callers=%s", session, + Debug.getCallers(3)); return null; } @@ -6049,11 +6102,21 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad); mRoot.dumpTopFocusedDisplayId(pw); mRoot.forAllDisplays(dc -> { + final int displayId = dc.getDisplayId(); final WindowState inputMethodTarget = dc.mInputMethodTarget; if (inputMethodTarget != null) { - pw.print(" mInputMethodTarget in display# "); pw.print(dc.getDisplayId()); + pw.print(" mInputMethodTarget in display# "); pw.print(displayId); pw.print(' '); pw.println(inputMethodTarget); } + if (mAccessibilityController != null) { + final Region magnificationRegion = new Region(); + mAccessibilityController.getMagnificationRegionLocked(displayId, + magnificationRegion); + pw.print(" mMagnificationRegion in display# "); + pw.print(displayId); + pw.print(' '); + pw.println(magnificationRegion); + } }); pw.print(" mInTouchMode="); pw.println(mInTouchMode); pw.print(" mLastDisplayFreezeDuration="); @@ -7359,7 +7422,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { WindowState window = mWindowMap.get(token); if (window != null) { - return UserHandle.getUserId(window.mOwnerUid); + return window.mShowUserId; } return UserHandle.USER_NULL; } @@ -7976,6 +8039,33 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** Set layer tracing flags. */ + public void setLayerTracingFlags(int flags) { + mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP, + "setLayerTracingFlags"); + long token = Binder.clearCallingIdentity(); + try { + Parcel data = null; + try { + IBinder sf = ServiceManager.getService("SurfaceFlinger"); + if (sf != null) { + data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(flags); + sf.transact(1033 /* LAYER_TRACE_FLAGS_CODE */, data, null, 0 /* flags */); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set layer tracing flags"); + } finally { + if (data != null) { + data.recycle(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) { if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 8e7585ae4bfa..c11c29b5deb6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -191,7 +191,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; -import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; @@ -269,6 +268,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int mAppOp; // UserId and appId of the owner. Don't display windows of non-current user. final int mOwnerUid; + /** + * Requested userId, if this is not equals with the userId from mOwnerUid, then this window is + * created for secondary user. + * Use this member instead of get userId from mOwnerUid while query for visibility. + */ + final int mShowUserId; /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */ final boolean mOwnerCanAddInternalSystemWindow; final WindowId mWindowId; @@ -806,8 +811,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) { - this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, + int viewVisibility, int ownerId, int showUserId, + boolean ownerCanAddInternalSystemWindow) { + this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId, ownerCanAddInternalSystemWindow, new PowerManagerWrapper() { @Override public void wakeUp(long time, @WakeReason int reason, String details) { @@ -823,8 +829,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow, - PowerManagerWrapper powerManagerWrapper) { + int viewVisibility, int ownerId, int showUserId, + boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) { super(service); mSession = s; mClient = c; @@ -832,6 +838,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mToken = token; mActivityRecord = mToken.asActivityRecord(); mOwnerUid = ownerId; + mShowUserId = showUserId; mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow; mWindowId = new WindowId(this); mAttrs.copyFrom(a); @@ -3275,7 +3282,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } return win.showForAllUsers() - || mWmService.isCurrentProfile(UserHandle.getUserId(win.mOwnerUid)); + || mWmService.isCurrentProfile(win.mShowUserId); } private static void applyInsets(Region outRegion, Rect frame, Rect inset) { @@ -3439,13 +3446,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP getMergedConfiguration(mLastReportedConfiguration); mLastConfigReportedToClient = true; + final boolean reportOrientation = mReportOrientationChanged; + // Always reset these states first, so if {@link IWindow#resized} fails, this + // window won't be added to {@link WindowManagerService#mResizingWindows} and set + // {@link #mOrientationChanging} to true again by {@link #updateResizingWindowIfNeeded} + // that may cause WINDOW_FREEZE_TIMEOUT because resizing the client keeps failing. + mReportOrientationChanged = false; + mDragResizingChangeReported = true; + mWinAnimator.mSurfaceResized = false; + mWindowFrames.resetInsetsChanged(); + final Rect frame = mWindowFrames.mCompatFrame; final Rect contentInsets = mWindowFrames.mLastContentInsets; final Rect visibleInsets = mWindowFrames.mLastVisibleInsets; final Rect stableInsets = mWindowFrames.mLastStableInsets; final MergedConfiguration mergedConfiguration = mLastReportedConfiguration; final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING; - final boolean forceRelayout = mReportOrientationChanged || isDragResizeChanged(); + final boolean forceRelayout = reportOrientation || isDragResizeChanged(); final int displayId = getDisplayId(); final DisplayCutout displayCutout = getWmDisplayCutout().getDisplayCutout(); @@ -3454,29 +3471,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mergedConfiguration, getBackdropFrame(frame), forceRelayout, getDisplayContent().getDisplayPolicy().areSystemBarsForcedShownLw(this), displayId, new DisplayCutout.ParcelableWrapper(displayCutout)); - mDragResizingChangeReported = true; if (mWmService.mAccessibilityController != null) { mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(displayId); } updateLocationInParentDisplayIfNeeded(); - - mWindowFrames.resetInsetsChanged(); - mWinAnimator.mSurfaceResized = false; - mReportOrientationChanged = false; } catch (RemoteException e) { + // Cancel orientation change of this window to avoid blocking unfreeze display. setOrientationChanging(false); mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - mWmService.mDisplayFreezeTime); - // We are assuming the hosting process is dead or in a zombie state. - Slog.w(TAG, "Failed to report 'resized' to the client of " + this - + ", removing this window."); - mWmService.mPendingRemove.add(this); - mWmService.mWindowPlacerLocked.requestTraversal(); + Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + boolean isClientLocal() { + return mClient instanceof IWindow.Stub; + } + void updateLocationInParentDisplayIfNeeded() { final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size(); // If there is any embedded display which is re-parented to this window, we need to @@ -3789,7 +3802,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); - proto.write(USER_ID, UserHandle.getUserId(mOwnerUid)); + proto.write(USER_ID, mShowUserId); final CharSequence title = getWindowTag(); if (title != null) { proto.write(TITLE, title.toString()); @@ -3973,7 +3986,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLastTitle = title; mWasExiting = mAnimatingExit; mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this)) - + " u" + UserHandle.getUserId(mOwnerUid) + + " u" + mShowUserId + " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}"); } return mStringNameCache; @@ -4268,9 +4281,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP logPerformShow("performShow on "); final int drawState = mWinAnimator.mDrawState; - if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) - && mAttrs.type != TYPE_APPLICATION_STARTING && mActivityRecord != null) { - mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); + if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) { + if (mAttrs.type != TYPE_APPLICATION_STARTING) { + mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); + } else { + mActivityRecord.onStartingWindowDrawn(); + } } if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) { @@ -4982,7 +4998,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override - boolean isAnimating(int flags) { + boolean isAnimating(int flags, int typesToCheck) { // If we are an inset provider, all our animations are driven by the inset client, so we // aren't really animating. @@ -4990,7 +5006,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mControllableInsetProvider != null) { return false; } - return super.isAnimating(flags); + return super.isAnimating(flags, typesToCheck); } void startAnimation(Animation anim) { @@ -5231,23 +5247,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { super.onAnimationLeashCreated(t, leash); - - // Leash is now responsible for position, so set our position to 0. - t.setPosition(mSurfaceControl, 0, 0); - mLastSurfacePosition.set(0, 0); } @Override public void onAnimationLeashLost(Transaction t) { super.onAnimationLeashLost(t); - updateSurfacePosition(t); } @Override - void updateSurfacePosition() { - updateSurfacePosition(getPendingTransaction()); - } - @VisibleForTesting void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index f7ea953e5a01..55e6ab76188d 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -126,7 +126,7 @@ class WindowSurfaceController { if (useBLAST) { mBLASTSurfaceControl = win.makeSurface() .setParent(mSurfaceControl) - .setName("BLAST Adapter Layer") + .setName(name + "(BLAST)") .setHidden(false) .setBLASTLayer() .build(); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 7457a1d05335..e34b81654c72 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Process.INVALID_UID; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -44,6 +45,7 @@ import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.InsetsState; @@ -106,6 +108,11 @@ class WindowToken extends WindowContainer<WindowState> { @VisibleForTesting final boolean mFromClientToken; + private DeathRecipient mDeathRecipient; + private boolean mBinderDied = false; + + private final int mOwnerUid; + /** * Used to fix the transform of the token to be rotated to a rotation different than it's * display. The window frames and surfaces corresponding to this token will be layouted and @@ -165,6 +172,30 @@ class WindowToken extends WindowContainer<WindowState> { } } + private class DeathRecipient implements IBinder.DeathRecipient { + private boolean mHasUnlinkToDeath = false; + + @Override + public void binderDied() { + synchronized (mWmService.mGlobalLock) { + mBinderDied = true; + removeImmediately(); + } + } + + void linkToDeath() throws RemoteException { + token.linkToDeath(DeathRecipient.this, 0); + } + + void unlinkToDeath() { + if (mHasUnlinkToDeath) { + return; + } + token.unlinkToDeath(DeathRecipient.this, 0); + mHasUnlinkToDeath = true; + } + } + /** * Compares two child window of this token and returns -1 if the first is lesser than the * second in terms of z-order and 1 otherwise. @@ -193,23 +224,35 @@ class WindowToken extends WindowContainer<WindowState> { WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) { - this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, + this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, INVALID_UID, roundedCornerOverlay, false /* fromClientToken */); } WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty, - DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay, - boolean fromClientToken) { + DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid, + boolean roundedCornerOverlay, boolean fromClientToken) { super(service); token = _token; windowType = type; mPersistOnEmpty = persistOnEmpty; mOwnerCanManageAppTokens = ownerCanManageAppTokens; + mOwnerUid = ownerUid; mRoundedCornerOverlay = roundedCornerOverlay; mFromClientToken = fromClientToken; if (dc != null) { dc.addWindowToken(token, this); } + if (shouldReportToClient()) { + try { + mDeathRecipient = new DeathRecipient(); + mDeathRecipient.linkToDeath(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to add window token with type " + windowType + " on " + + "display " + dc.getDisplayId(), e); + mDeathRecipient = null; + return; + } + } } void removeAllWindowsIfPossible() { @@ -222,7 +265,7 @@ class WindowToken extends WindowContainer<WindowState> { } void setExiting() { - if (mChildren.size() == 0) { + if (isEmpty()) { super.removeImmediately(); return; } @@ -340,6 +383,21 @@ class WindowToken extends WindowContainer<WindowState> { // Needs to occur after the token is removed from the display above to avoid attempt at // duplicate removal of this window container from it's parent. super.removeImmediately(); + + reportWindowTokenRemovedToClient(); + } + + private void reportWindowTokenRemovedToClient() { + if (!shouldReportToClient()) { + return; + } + mDeathRecipient.unlinkToDeath(); + IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); + try { + windowTokenClient.onWindowTokenRemoved(); + } catch (RemoteException e) { + ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); + } } @Override @@ -361,17 +419,9 @@ class WindowToken extends WindowContainer<WindowState> { } void reportConfigToWindowTokenClient() { - if (asActivityRecord() != null) { - // Activities are updated through ATM callbacks. - return; - } - - // Unfortunately, this WindowToken is not from WindowContext so it cannot handle - // its own configuration changes. - if (!mFromClientToken) { + if (!shouldReportToClient()) { return; } - final Configuration config = getConfiguration(); final int displayId = getDisplayContent().getDisplayId(); if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { @@ -383,16 +433,26 @@ class WindowToken extends WindowContainer<WindowState> { mLastReportedDisplay = displayId; IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); - if (windowTokenClient != null) { - try { - windowTokenClient.onConfigurationChanged(config, displayId); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, - "Could not report config changes to the window token client."); - } + try { + windowTokenClient.onConfigurationChanged(config, displayId); + } catch (RemoteException e) { + ProtoLog.w(WM_ERROR, + "Could not report config changes to the window token client."); } } + /** + * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and + * registered from client side. + */ + private boolean shouldReportToClient() { + // Only report to client for WindowToken because Activities are updated through ATM + // callbacks. + return asActivityRecord() == null + // Report to {@link android.view.WindowTokenClient} if this token was registered from it. + && mFromClientToken && !mBinderDied; + } + @Override void assignLayer(SurfaceControl.Transaction t, int layer) { if (windowType == TYPE_DOCK_DIVIDER) { @@ -494,13 +554,12 @@ class WindowToken extends WindowContainer<WindowState> { // cleared and the configuration is restored from parent. if (!changed) { clearFixedRotationTransform(null /* applyDisplayRotation */); - onConfigurationChanged(getParent().getConfiguration()); } } /** - * Clears the transform and apply display rotation if the action is given. The caller needs to - * refresh the configuration of this container after this method call. + * Clears the transform and apply display rotation if the action is given. If the display will + * not rotate, the transformed containers are restored to their original states. */ void clearFixedRotationTransform(Runnable applyDisplayRotation) { final FixedRotationTransformState state = mFixedRotationTransformState; @@ -514,6 +573,12 @@ class WindowToken extends WindowContainer<WindowState> { state.mIsTransforming = false; if (applyDisplayRotation != null) { applyDisplayRotation.run(); + } else { + // The display will not rotate to the rotation of this container, let's cancel them. + for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) { + state.mAssociatedTokens.get(i).cancelFixedRotationTransform(); + } + cancelFixedRotationTransform(); } // The state is cleared at the end, because it is used to indicate that other windows can // use seamless rotation when applying rotation to display. @@ -523,6 +588,16 @@ class WindowToken extends WindowContainer<WindowState> { mFixedRotationTransformState = null; } + /** Restores the changes that applies to this container. */ + private void cancelFixedRotationTransform() { + final WindowContainer<?> parent = getParent(); + if (parent == null) { + // The window may be detached or detaching. + return; + } + onConfigurationChanged(parent.getConfiguration()); + } + @Override void resolveOverrideConfiguration(Configuration newParentConfig) { super.resolveOverrideConfiguration(newParentConfig); @@ -535,8 +610,8 @@ class WindowToken extends WindowContainer<WindowState> { } @Override - void updateSurfacePosition() { - super.updateSurfacePosition(); + void updateSurfacePosition(SurfaceControl.Transaction t) { + super.updateSurfacePosition(t); if (isFixedRotationTransforming()) { // The window is layouted in a simulated rotated display but the real display hasn't // rotated, so here transforms its surface to fit in the real display. @@ -616,4 +691,8 @@ class WindowToken extends WindowContainer<WindowState> { int getWindowLayerFromType() { return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens); } + + int getOwnerUid() { + return mOwnerUid; + } } diff --git a/services/core/xsd/vts/Android.bp b/services/core/xsd/vts/Android.bp index 636d11069731..a942108667a5 100644 --- a/services/core/xsd/vts/Android.bp +++ b/services/core/xsd/vts/Android.bp @@ -36,7 +36,7 @@ cc_test { ], test_suites: [ "general-tests", - "vts-core" + "vts" ], test_config: "vts_defaultPermissions_validate_test.xml", } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index eed39e182a33..1da074002456 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -87,6 +87,9 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.os.UserManagerInternal.OWNER_TYPE_DEVICE_OWNER; +import static android.os.UserManagerInternal.OWNER_TYPE_PROFILE_OWNER; +import static android.os.UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Telephony.Carriers.DPC_URI; @@ -284,6 +287,7 @@ import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pm.RestrictionsSet; import com.android.server.pm.UserRestrictionsUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.storage.DeviceStorageMonitorInternal; @@ -322,6 +326,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Predicate; /** * Implementation of the device policy APIs. @@ -1828,6 +1833,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { info = deviceAdminInfo; } + Bundle addSyntheticRestrictions(Bundle restrictions) { + if (disableCamera) { + restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true); + } + if (requireAutoTime) { + restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true); + } + return restrictions; + } + + static Bundle removeDeprecatedRestrictions(Bundle restrictions) { + for (String deprecatedRestriction: DEPRECATED_USER_RESTRICTIONS) { + restrictions.remove(deprecatedRestriction); + } + return restrictions; + } + + static Bundle filterRestrictions(Bundle restrictions, Predicate<String> filter) { + Bundle result = new Bundle(); + for (String key : restrictions.keySet()) { + if (!restrictions.getBoolean(key)) { + continue; + } + if (filter.test(key)) { + result.putBoolean(key, true); + } + } + return result; + } + + Bundle getEffectiveRestrictions() { + return addSyntheticRestrictions( + removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions()))); + } + + Bundle getLocalUserRestrictions(int adminType) { + return filterRestrictions(getEffectiveRestrictions(), + key -> UserRestrictionsUtils.isLocal(adminType, key)); + } + + Bundle getGlobalUserRestrictions(int adminType) { + return filterRestrictions(getEffectiveRestrictions(), + key -> UserRestrictionsUtils.isGlobal(adminType, key)); + } + void dump(IndentingPrintWriter pw) { pw.print("uid="); pw.println(getUid()); pw.print("testOnlyAdmin="); @@ -2652,10 +2702,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slog.i(LOG_TAG, "Clearing the DO..."); final ComponentName doAdminReceiver = doAdmin.info.getComponent(); clearDeviceOwnerLocked(doAdmin, doUserId); - // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if - // it is worth the complexity to make it more robust. Slog.i(LOG_TAG, "Removing admin artifacts..."); - // TODO(b/143516163): Clean up application restrictions in UserManager. + // TODO(b/149075700): Clean up application restrictions in UserManager. removeAdminArtifacts(doAdminReceiver, doUserId); Slog.i(LOG_TAG, "Migration complete."); @@ -2697,16 +2745,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // The following policies weren't available to PO, but will be available after migration. parentAdmin.disableCamera = doAdmin.disableCamera; - - // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance. - // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture; - // parentAdmin.accountTypesWithManagementDisabled.addAll( - // doAdmin.accountTypesWithManagementDisabled); + parentAdmin.requireAutoTime = doAdmin.requireAutoTime; + parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture; + parentAdmin.accountTypesWithManagementDisabled.addAll( + doAdmin.accountTypesWithManagementDisabled); moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin); - - // TODO(b/143516163): migrate network and security logging state, currently they are - // turned off when DO is removed. } private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { @@ -2726,7 +2770,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * a managed profile. */ @GuardedBy("getLockObject()") - void applyManagedProfileRestrictionIfDeviceOwnerLocked() { + private void applyManagedProfileRestrictionIfDeviceOwnerLocked() { final int doUserId = mOwners.getDeviceOwnerUserId(); if (doUserId == UserHandle.USER_NULL) { logIfVerbose("No DO found, skipping application of restriction."); @@ -2772,7 +2816,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) { profileOwner.ensureUserRestrictions().putBoolean( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); - saveUserRestrictionsLocked(userId, /* parent = */ false); + saveUserRestrictionsLocked(userId); mInjector.settingsSecurePutIntForUser( Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId); } @@ -2803,7 +2847,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet); Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet); - saveUserRestrictionsLocked(userId, /* parent = */ false); + saveUserRestrictionsLocked(userId); } } @@ -3950,11 +3994,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mOwners.systemReady(); break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: - maybeStartSecurityLogMonitorOnActivityManagerReady(); synchronized (getLockObject()) { migrateToProfileOnOrganizationOwnedDeviceIfCompLocked(); applyManagedProfileRestrictionIfDeviceOwnerLocked(); } + maybeStartSecurityLogMonitorOnActivityManagerReady(); final int userId = getManagedUserId(UserHandle.USER_SYSTEM); if (userId >= 0) { updatePersonalAppSuspension(userId, false /* running */); @@ -7790,16 +7834,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final int userHandle = UserHandle.getCallingUserId(); + boolean requireAutoTimeChanged = false; synchronized (getLockObject()) { ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (admin.requireAutoTime != required) { admin.requireAutoTime = required; saveSettingsLocked(userHandle); + requireAutoTimeChanged = true; } } - - // TODO: (b/145604635) Add upgrade case + // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so propagate + // updated restrictions to the framework. + if (requireAutoTimeChanged) { + pushUserRestrictions(userHandle); + } // Turn AUTO_TIME on in settings if it is required if (required) { mInjector.binderWithCleanCallingIdentity( @@ -8222,9 +8271,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } // Tell the user manager that the restrictions have changed. - final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; - pushUserRestrictions(affectedUserId); + pushUserRestrictions(userHandle); + final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; if (SecurityLog.isLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_CAMERA_POLICY_SET, who.getPackageName(), userHandle, affectedUserId, disabled ? 1 : 0); @@ -10806,10 +10855,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Cannot use the parent instance in Device Owner mode"); } } else { - if (!(UserRestrictionsUtils.canProfileOwnerChange(key, userHandle) || ( - isProfileOwnerOfOrganizationOwnedDevice(activeAdmin) && parent - && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange( - key)))) { + boolean profileOwnerCanChangeOnItself = !parent + && UserRestrictionsUtils.canProfileOwnerChange(key, userHandle); + boolean orgOwnedProfileOwnerCanChangesGlobally = parent + && isProfileOwnerOfOrganizationOwnedDevice(activeAdmin) + && UserRestrictionsUtils + .canProfileOwnerOfOrganizationOwnedDeviceChange(key); + + if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangesGlobally) { throw new SecurityException("Profile owner cannot set user restriction " + key); } } @@ -10821,7 +10874,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else { restrictions.remove(key); } - saveUserRestrictionsLocked(userHandle, parent); + saveUserRestrictionsLocked(userHandle); } final int eventId = enabledFromThisOwner ? DevicePolicyEnums.ADD_USER_RESTRICTION @@ -10839,91 +10892,65 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void saveUserRestrictionsLocked(int userId, boolean parent) { + private void saveUserRestrictionsLocked(int userId) { saveSettingsLocked(userId); - pushUserRestrictions(parent ? getProfileParentId(userId) : userId); + pushUserRestrictions(userId); sendChangedNotification(userId); } - private void pushUserRestrictions(int userId) { + /** + * Pushes the user restrictions originating from a specific user. + * + * If called by the profile owner of an organization-owned device, the global and local + * user restrictions will be an accumulation of the global user restrictions from the profile + * owner active admin and its parent active admin. The key of the local user restrictions set + * will be the target user id. + */ + private void pushUserRestrictions(int originatingUserId) { + final Bundle global; + final RestrictionsSet local = new RestrictionsSet(); + final boolean isDeviceOwner; synchronized (getLockObject()) { - final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId); - Bundle userRestrictions = null; - final int restrictionOwnerType; - final int originatingUserId; - + isDeviceOwner = mOwners.isDeviceOwnerUserId(originatingUserId); if (isDeviceOwner) { final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (deviceOwner == null) { return; // Shouldn't happen. } - userRestrictions = addOrRemoveDisableCameraRestriction( - deviceOwner.userRestrictions, deviceOwner); - restrictionOwnerType = UserManagerInternal.OWNER_TYPE_DEVICE_OWNER; - originatingUserId = deviceOwner.getUserHandle().getIdentifier(); + global = deviceOwner.getGlobalUserRestrictions(OWNER_TYPE_DEVICE_OWNER); + local.updateRestrictions(originatingUserId, deviceOwner.getLocalUserRestrictions( + OWNER_TYPE_DEVICE_OWNER)); } else { - final ActiveAdmin profileOwnerOfOrganizationOwnedDevice = - getProfileOwnerOfOrganizationOwnedDeviceLocked(userId); - - // If profile owner of an organization owned device, the restrictions will be - // pushed to the parent instance. - if (profileOwnerOfOrganizationOwnedDevice != null && !isManagedProfile(userId)) { - restrictionOwnerType = - UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE; - final ActiveAdmin parent = profileOwnerOfOrganizationOwnedDevice - .getParentActiveAdmin(); - userRestrictions = parent.userRestrictions; - userRestrictions = addOrRemoveDisableCameraRestriction(userRestrictions, - parent); - originatingUserId = - profileOwnerOfOrganizationOwnedDevice.getUserHandle().getIdentifier(); - } else { - final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId); - - if (profileOwner != null) { - userRestrictions = profileOwner.userRestrictions; - restrictionOwnerType = UserManagerInternal.OWNER_TYPE_PROFILE_OWNER; - originatingUserId = profileOwner.getUserHandle().getIdentifier(); - } else { - restrictionOwnerType = UserManagerInternal.OWNER_TYPE_NO_OWNER; - originatingUserId = userId; - } - userRestrictions = addOrRemoveDisableCameraRestriction( - userRestrictions, userId); + final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(originatingUserId); + if (profileOwner == null) { + return; + } + global = profileOwner.getGlobalUserRestrictions(OWNER_TYPE_PROFILE_OWNER); + local.updateRestrictions(originatingUserId, profileOwner.getLocalUserRestrictions( + OWNER_TYPE_PROFILE_OWNER)); + // Global (device-wide) and local user restrictions set by the profile owner of an + // organization-owned device are stored in the parent ActiveAdmin instance. + if (isProfileOwnerOfOrganizationOwnedDevice( + profileOwner.getUserHandle().getIdentifier())) { + // The global restrictions set on the parent ActiveAdmin instance need to be + // merged with the global restrictions set on the profile owner ActiveAdmin + // instance, since both are to be applied device-wide. + UserRestrictionsUtils.merge(global, + profileOwner.getParentActiveAdmin().getGlobalUserRestrictions( + OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); + // The local restrictions set on the parent ActiveAdmin instance are only to be + // applied to the primary user. They therefore need to be added the local + // restriction set with the primary user id as the key, in this case the + // primary user id is the target user. + local.updateRestrictions( + getProfileParentId(profileOwner.getUserHandle().getIdentifier()), + profileOwner.getParentActiveAdmin().getLocalUserRestrictions( + OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); } } - // Remove deprecated restrictions. - for (String deprecatedRestriction: DEPRECATED_USER_RESTRICTIONS) { - userRestrictions.remove(deprecatedRestriction); - } - mUserManagerInternal.setDevicePolicyUserRestrictions(originatingUserId, - userRestrictions, restrictionOwnerType); - } - } - - private Bundle addOrRemoveDisableCameraRestriction(Bundle userRestrictions, ActiveAdmin admin) { - if (userRestrictions == null) { - userRestrictions = new Bundle(); - } - if (admin.disableCamera) { - userRestrictions.putBoolean(UserManager.DISALLOW_CAMERA, true); - } else { - userRestrictions.remove(UserManager.DISALLOW_CAMERA); } - return userRestrictions; - } - - private Bundle addOrRemoveDisableCameraRestriction(Bundle userRestrictions, int userId) { - if (userRestrictions == null) { - userRestrictions = new Bundle(); - } - if (getCameraDisabled(/* who= */ null, userId, /* mergeDeviceOwnerRestriction= */ - false)) { - userRestrictions.putBoolean(UserManager.DISALLOW_CAMERA, true); - } else { - userRestrictions.remove(UserManager.DISALLOW_CAMERA); - } - return userRestrictions; + mUserManagerInternal.setDevicePolicyUserRestrictions(originatingUserId, global, local, + isDeviceOwner); } @Override @@ -12432,6 +12459,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return DevicePolicyManagerService.this.getAllCrossProfilePackages(); } + @Override + public List<String> getDefaultCrossProfilePackages() { + return DevicePolicyManagerService.this.getDefaultCrossProfilePackages(); + } + /** * Sends the {@code intent} to the packages with cross profile capabilities. * diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index 02bb0bc3e49c..b13d33054e19 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -50,7 +50,6 @@ cc_defaults { "libbinder", "libcrypto", "libcutils", - "libdataloader", "libincfs", "liblog", "libz", diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 97de1800cae2..aabc58c3a297 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -17,6 +17,7 @@ #include "BinderIncrementalService.h" #include <android-base/logging.h> +#include <android-base/no_destructor.h> #include <binder/IResultReceiver.h> #include <binder/PermissionCache.h> #include <incfs.h> @@ -93,8 +94,8 @@ BinderIncrementalService* BinderIncrementalService::start() { } status_t BinderIncrementalService::dump(int fd, const Vector<String16>&) { - static const String16 kDump("android.permission.DUMP"); - if (!PermissionCache::checkCallingPermission(kDump)) { + static const android::base::NoDestructor<String16> kDump("android.permission.DUMP"); + if (!PermissionCache::checkCallingPermission(*kDump)) { return PERMISSION_DENIED; } mImpl.onDump(fd); @@ -155,11 +156,6 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } -binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) { - *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs); - return ok(); -} - binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path, int32_t* _aidl_return) { *_aidl_return = mImpl.makeDir(storageId, path); diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index d0357d924586..28613e101b7c 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -71,7 +71,6 @@ public: binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, const std::string& abi, bool* _aidl_return) final; - binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 0da167303ccd..eb65a2ddc5f1 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -17,10 +17,10 @@ #define LOG_TAG "IncrementalService" #include "IncrementalService.h" -#include "IncrementalServiceValidation.h" #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/no_destructor.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> @@ -52,7 +52,7 @@ using namespace android::content::pm; namespace fs = std::filesystem; constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS"; -constexpr const char* kOpUsage = "android:get_usage_stats"; +constexpr const char* kOpUsage = "android:loader_usage_stats"; namespace android::incremental { @@ -74,7 +74,7 @@ struct Constants { }; static const Constants& constants() { - static Constants c; + static constexpr Constants c; return c; } @@ -160,8 +160,13 @@ std::string makeBindMdName() { } } // namespace +const bool IncrementalService::sEnablePerfLogging = + android::base::GetBoolProperty("incremental.perflogging", false); + IncrementalService::IncFsMount::~IncFsMount() { - incrementalService.mDataLoaderManager->destroyDataLoader(mountId); + if (dataLoaderStub) { + dataLoaderStub->destroy(); + } LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\''; for (auto&& [target, _] : bindPoints) { LOG(INFO) << "\tbind: " << target; @@ -286,9 +291,12 @@ void IncrementalService::onDump(int fd) { dprintf(fd, "\t\tmountId: %d\n", mnt.mountId); dprintf(fd, "\t\troot: %s\n", mnt.root.c_str()); dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load()); - dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load()); - { - const auto& params = mnt.dataLoaderParams; + if (mnt.dataLoaderStub) { + const auto& dataLoaderStub = *mnt.dataLoaderStub; + dprintf(fd, "\t\tdataLoaderStatus: %d\n", dataLoaderStub.status()); + dprintf(fd, "\t\tdataLoaderStartRequested: %s\n", + dataLoaderStub.startRequested() ? "true" : "false"); + const auto& params = dataLoaderStub.params(); dprintf(fd, "\t\tdataLoaderParams:\n"); dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str()); dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str()); @@ -319,10 +327,9 @@ void IncrementalService::onDump(int fd) { } } -std::optional<std::future<void>> IncrementalService::onSystemReady() { - std::promise<void> threadFinished; +void IncrementalService::onSystemReady() { if (mSystemReady.exchange(true)) { - return {}; + return; } std::vector<IfsMountPtr> mounts; @@ -336,8 +343,8 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { } } + /* TODO(b/151241369): restore data loaders on reboot. std::thread([this, mounts = std::move(mounts)]() { - /* TODO(b/151241369): restore data loaders on reboot. for (auto&& ifs : mounts) { if (prepareDataLoader(*ifs)) { LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId; @@ -346,10 +353,8 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId; } } - */ - mPrepareDataLoaders.set_value_at_thread_exit(); }).detach(); - return mPrepareDataLoaders.get_future(); + */ } auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { @@ -466,15 +471,13 @@ StorageId IncrementalService::createStorage( return kInvalidStorageId; } - ifs->dataLoaderParams = std::move(dataLoaderParams); - { metadata::Mount m; m.mutable_storage()->set_id(ifs->mountId); - m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type); - m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName); - m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className); - m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments); + m.mutable_loader()->set_type((int)dataLoaderParams.type); + m.mutable_loader()->set_package_name(dataLoaderParams.packageName); + m.mutable_loader()->set_class_name(dataLoaderParams.className); + m.mutable_loader()->set_arguments(dataLoaderParams.arguments); const auto metadata = m.SerializeAsString(); m.mutable_loader()->release_arguments(); m.mutable_loader()->release_class_name(); @@ -502,14 +505,20 @@ StorageId IncrementalService::createStorage( // Done here as well, all data structures are in good state. secondCleanupOnFailure.release(); - if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) { - LOG(ERROR) << "prepareDataLoader() failed"; - deleteStorageLocked(*ifs, std::move(l)); - return kInvalidStorageId; - } + auto dataLoaderStub = + prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener); + CHECK(dataLoaderStub); mountIt->second = std::move(ifs); l.unlock(); + + if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->create()) { + // failed to create data loader + LOG(ERROR) << "initializeDataLoader() failed"; + deleteStorage(dataLoaderStub->id()); + return kInvalidStorageId; + } + LOG(INFO) << "created storage " << mountId; return mountId; } @@ -579,28 +588,33 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { const auto ifs = getIfs(storageId); if (!ifs) { + LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId; return -EINVAL; } - ifs->dataLoaderFilesystemParams.readLogsEnabled = enableReadLogs; + const auto& params = ifs->dataLoaderStub->params(); if (enableReadLogs) { - // We never unregister the callbacks, but given a restricted number of data loaders and even fewer asking for read log access, should be ok. - registerAppOpsCallback(ifs->dataLoaderParams.packageName); + if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage, + params.packageName.c_str()); + !status.isOk()) { + LOG(ERROR) << "checkPermission failed: " << status.toString8(); + return fromBinderStatus(status); + } } - return applyStorageParams(*ifs); -} + if (auto status = applyStorageParams(*ifs, enableReadLogs); !status.isOk()) { + LOG(ERROR) << "applyStorageParams failed: " << status.toString8(); + return fromBinderStatus(status); + } -int IncrementalService::applyStorageParams(IncFsMount& ifs) { - const bool enableReadLogs = ifs.dataLoaderFilesystemParams.readLogsEnabled; if (enableReadLogs) { - if (auto status = CheckPermissionForDataDelivery(kDataUsageStats, kOpUsage); - !status.isOk()) { - LOG(ERROR) << "CheckPermissionForDataDelivery failed: " << status.toString8(); - return fromBinderStatus(status); - } + registerAppOpsCallback(params.packageName); } + return 0; +} + +binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) { using unique_fd = ::android::base::unique_fd; ::android::os::incremental::IncrementalFileSystemControlParcel control; control.cmd.reset(unique_fd(dup(ifs.control.cmd()))); @@ -611,13 +625,7 @@ int IncrementalService::applyStorageParams(IncFsMount& ifs) { } std::lock_guard l(mMountOperationLock); - const auto status = mVold->setIncFsMountOptions(control, enableReadLogs); - if (!status.isOk()) { - LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8(); - return fromBinderStatus(status); - } - - return 0; + return mVold->setIncFsMountOptions(control, enableReadLogs); } void IncrementalService::deleteStorage(StorageId storageId) { @@ -700,8 +708,8 @@ IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) co const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const { auto it = mMounts.find(storage); if (it == mMounts.end()) { - static const IfsMountPtr kEmpty = {}; - return kEmpty; + static const android::base::NoDestructor<IfsMountPtr> kEmpty{}; + return *kEmpty; } return it->second; } @@ -722,7 +730,10 @@ int IncrementalService::bind(StorageId storage, std::string_view source, std::st if (storageInfo == ifs->storages.end()) { return -EINVAL; } - std::string normSource = normalizePathToStorage(ifs, storage, source); + std::string normSource = normalizePathToStorageLocked(storageInfo, source); + if (normSource.empty()) { + return -EINVAL; + } l.unlock(); std::unique_lock l2(mLock, std::defer_lock); return addBindMount(*ifs, storage, storageInfo->second.name, std::move(normSource), @@ -771,22 +782,28 @@ int IncrementalService::unbind(StorageId storage, std::string_view target) { return 0; } -std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr ifs, - StorageId storage, std::string_view path) { - const auto storageInfo = ifs->storages.find(storage); - if (storageInfo == ifs->storages.end()) { - return {}; - } +std::string IncrementalService::normalizePathToStorageLocked( + IncFsMount::StorageMap::iterator storageIt, std::string_view path) { std::string normPath; if (path::isAbsolute(path)) { normPath = path::normalize(path); + if (!path::startsWith(normPath, storageIt->second.name)) { + return {}; + } } else { - normPath = path::normalize(path::join(storageInfo->second.name, path)); + normPath = path::normalize(path::join(storageIt->second.name, path)); } - if (!path::startsWith(normPath, storageInfo->second.name)) { + return normPath; +} + +std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr& ifs, + StorageId storage, std::string_view path) { + std::unique_lock l(ifs->lock); + const auto storageInfo = ifs->storages.find(storage); + if (storageInfo == ifs->storages.end()) { return {}; } - return normPath; + return normalizePathToStorageLocked(storageInfo, path); } int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, @@ -794,7 +811,8 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m if (auto ifs = getIfs(storage)) { std::string normPath = normalizePathToStorage(ifs, storage, path); if (normPath.empty()) { - LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; + LOG(ERROR) << "Internal error: storageId " << storage + << " failed to normalize: " << path; return -EINVAL; } auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); @@ -802,10 +820,6 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; return err; } - std::vector<uint8_t> metadataBytes; - if (params.metadata.data && params.metadata.size > 0) { - metadataBytes.assign(params.metadata.data, params.metadata.data + params.metadata.size); - } return 0; } return -EINVAL; @@ -845,8 +859,9 @@ int IncrementalService::makeDirs(StorageId storageId, std::string_view path, int int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath, StorageId destStorageId, std::string_view newPath) { - if (auto ifsSrc = getIfs(sourceStorageId), ifsDest = getIfs(destStorageId); - ifsSrc && ifsSrc == ifsDest) { + auto ifsSrc = getIfs(sourceStorageId); + auto ifsDest = sourceStorageId == destStorageId ? ifsSrc : getIfs(destStorageId); + if (ifsSrc && ifsSrc == ifsDest) { std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath); std::string normNewPath = normalizePathToStorage(ifsDest, destStorageId, newPath); if (normOldPath.empty() || normNewPath.empty()) { @@ -976,34 +991,19 @@ std::vector<std::string> IncrementalService::listFiles(StorageId storage) const } bool IncrementalService::startLoading(StorageId storage) const { + DataLoaderStubPtr dataLoaderStub; { std::unique_lock l(mLock); const auto& ifs = getIfsLocked(storage); if (!ifs) { return false; } - if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { - ifs->dataLoaderStartRequested = true; - return true; + dataLoaderStub = ifs->dataLoaderStub; + if (!dataLoaderStub) { + return false; } } - return startDataLoader(storage); -} - -bool IncrementalService::startDataLoader(MountId mountId) const { - sp<IDataLoader> dataloader; - auto status = mDataLoaderManager->getDataLoader(mountId, &dataloader); - if (!status.isOk()) { - return false; - } - if (!dataloader) { - return false; - } - status = dataloader->start(mountId); - if (!status.isOk()) { - return false; - } - return true; + return dataLoaderStub->start(); } void IncrementalService::mountExistingImages() { @@ -1049,13 +1049,13 @@ bool IncrementalService::mountExistingImage(std::string_view root) { mNextId = std::max(mNextId, ifs->mountId + 1); // DataLoader params + DataLoaderParamsParcel dataLoaderParams; { - auto& dlp = ifs->dataLoaderParams; const auto& loader = mount.loader(); - dlp.type = (android::content::pm::DataLoaderType)loader.type(); - dlp.packageName = loader.package_name(); - dlp.className = loader.class_name(); - dlp.arguments = loader.arguments(); + dataLoaderParams.type = (android::content::pm::DataLoaderType)loader.type(); + dataLoaderParams.packageName = loader.package_name(); + dataLoaderParams.className = loader.class_name(); + dataLoaderParams.arguments = loader.arguments(); } std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints; @@ -1127,17 +1127,13 @@ bool IncrementalService::mountExistingImage(std::string_view root) { return true; } -bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, - const DataLoaderStatusListener* externalListener) { - if (!mSystemReady.load(std::memory_order_relaxed)) { - std::unique_lock l(ifs.lock); - return true; // eventually... - } - +IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader( + IncrementalService::IncFsMount& ifs, DataLoaderParamsParcel&& params, + const DataLoaderStatusListener* externalListener) { std::unique_lock l(ifs.lock); - if (ifs.dataLoaderStatus != -1) { + if (ifs.dataLoaderStub) { LOG(INFO) << "Skipped data loader preparation because it already exists"; - return true; + return ifs.dataLoaderStub; } FileSystemControlParcel fsControlParcel; @@ -1146,24 +1142,32 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, fsControlParcel.incremental->pendingReads.reset( base::unique_fd(::dup(ifs.control.pendingReads()))); fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs()))); - sp<IncrementalDataLoaderListener> listener = - new IncrementalDataLoaderListener(*this, - externalListener ? *externalListener - : DataLoaderStatusListener()); - bool created = false; - auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created); - if (!status.isOk() || !created) { - LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId; - return false; - } - return true; + fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId); + + ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params), + std::move(fsControlParcel), externalListener); + return ifs.dataLoaderStub; +} + +template <class Duration> +static long elapsedMcs(Duration start, Duration end) { + return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); } -// Extract lib filse from zip, create new files in incfs and write data to them +// Extract lib files from zip, create new files in incfs and write data to them bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi) { + namespace sc = std::chrono; + using Clock = sc::steady_clock; + auto start = Clock::now(); + const auto ifs = getIfs(storage); + if (!ifs) { + LOG(ERROR) << "Invalid storage " << storage; + return false; + } + // First prepare target directories if they don't exist yet if (auto res = makeDirs(storage, libDirRelativePath, 0755)) { LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath @@ -1171,181 +1175,271 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ return false; } - std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data())); + auto mkDirsTs = Clock::now(); + + std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(path::c_str(apkFullPath))); if (!zipFile) { LOG(ERROR) << "Failed to open zip file at " << apkFullPath; return false; } void* cookie = nullptr; const auto libFilePrefix = path::join(constants().libDir, abi); - if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, - constants().libSuffix.data() /* suffix */)) { + if (!zipFile->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, + constants().libSuffix.data() /* suffix */)) { LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; return false; } + auto endIteration = [&zipFile](void* cookie) { zipFile->endIteration(cookie); }; + auto iterationCleaner = std::unique_ptr<void, decltype(endIteration)>(cookie, endIteration); + + auto openZipTs = Clock::now(); + + std::vector<IncFsDataBlock> instructions; ZipEntryRO entry = nullptr; - bool success = true; - while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) { + while ((entry = zipFile->nextEntry(cookie)) != nullptr) { + auto startFileTs = Clock::now(); + char fileName[PATH_MAX]; - if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) { + if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) { continue; } const auto libName = path::basename(fileName); const auto targetLibPath = path::join(libDirRelativePath, libName); const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath); // If the extract file already exists, skip - struct stat st; - if (stat(targetLibPathAbsolute.c_str(), &st) == 0) { - LOG(INFO) << "Native lib file already exists: " << targetLibPath - << "; skipping extraction"; + if (access(targetLibPathAbsolute.c_str(), F_OK) == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Native lib file already exists: " << targetLibPath + << "; skipping extraction, spent " + << elapsedMcs(startFileTs, Clock::now()) << "mcs"; + } continue; } - uint32_t uncompressedLen; - if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr, - nullptr, nullptr)) { + uint32_t uncompressedLen, compressedLen; + if (!zipFile->getEntryInfo(entry, nullptr, &uncompressedLen, &compressedLen, nullptr, + nullptr, nullptr)) { LOG(ERROR) << "Failed to read native lib entry: " << fileName; - success = false; - break; + return false; } // Create new lib file without signature info - incfs::NewFileParams libFileParams{}; - libFileParams.size = uncompressedLen; - libFileParams.signature = {}; - // Metadata of the new lib file is its relative path - IncFsSpan libFileMetadata; - libFileMetadata.data = targetLibPath.c_str(); - libFileMetadata.size = targetLibPath.size(); - libFileParams.metadata = libFileMetadata; + incfs::NewFileParams libFileParams = { + .size = uncompressedLen, + .signature = {}, + // Metadata of the new lib file is its relative path + .metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()}, + }; incfs::FileId libFileId = idFromMetadata(targetLibPath); - if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) { + if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0777, libFileId, + libFileParams)) { LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res; - success = false; // If one lib file fails to be created, abort others as well - break; + return false; } + + auto makeFileTs = Clock::now(); + // If it is a zero-byte file, skip data writing if (uncompressedLen == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, makeFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs); + } continue; } // Write extracted data to new file - std::vector<uint8_t> libData(uncompressedLen); - if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) { + // NOTE: don't zero-initialize memory, it may take a while + auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[uncompressedLen]); + if (!zipFile->uncompressEntry(entry, libData.get(), uncompressedLen)) { LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName; - success = false; - break; + return false; } + + auto extractFileTs = Clock::now(); + const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId); if (!writeFd.ok()) { LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; - success = false; - break; + return false; } - const int numBlocks = uncompressedLen / constants().blockSize + 1; - std::vector<IncFsDataBlock> instructions; - auto remainingData = std::span(libData); - for (int i = 0; i < numBlocks - 1; i++) { + + auto openFileTs = Clock::now(); + + const int numBlocks = (uncompressedLen + constants().blockSize - 1) / constants().blockSize; + instructions.clear(); + instructions.reserve(numBlocks); + auto remainingData = std::span(libData.get(), uncompressedLen); + for (int i = 0; i < numBlocks; i++) { + const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size()); auto inst = IncFsDataBlock{ - .fileFd = writeFd, + .fileFd = writeFd.get(), .pageIndex = static_cast<IncFsBlockIndex>(i), .compression = INCFS_COMPRESSION_KIND_NONE, .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(constants().blockSize), + .dataSize = blockSize, .data = reinterpret_cast<const char*>(remainingData.data()), }; instructions.push_back(inst); - remainingData = remainingData.subspan(constants().blockSize); + remainingData = remainingData.subspan(blockSize); } - // Last block - auto inst = IncFsDataBlock{ - .fileFd = writeFd, - .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1), - .compression = INCFS_COMPRESSION_KIND_NONE, - .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(remainingData.size()), - .data = reinterpret_cast<const char*>(remainingData.data()), - }; - instructions.push_back(inst); + auto prepareInstsTs = Clock::now(); + size_t res = mIncFs->writeBlocks(instructions); if (res != instructions.size()) { LOG(ERROR) << "Failed to write data into: " << targetLibPath; - success = false; + return false; + } + + if (sEnablePerfLogging) { + auto endFileTs = Clock::now(); + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, endFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs) + << " extract: " << elapsedMcs(makeFileTs, extractFileTs) + << " open: " << elapsedMcs(extractFileTs, openFileTs) + << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs) + << " write:" << elapsedMcs(prepareInstsTs, endFileTs); } - instructions.clear(); } - zipFile.get()->endIteration(cookie); - return success; -} -void IncrementalService::registerAppOpsCallback(const std::string& packageName) { - if (packageName.empty()) { - return; + if (sEnablePerfLogging) { + auto end = Clock::now(); + LOG(INFO) << "incfs: configureNativeBinaries complete in " << elapsedMcs(start, end) + << "mcs, make dirs: " << elapsedMcs(start, mkDirsTs) + << " open zip: " << elapsedMcs(mkDirsTs, openZipTs) + << " extract all: " << elapsedMcs(openZipTs, end); } + return true; +} + +void IncrementalService::registerAppOpsCallback(const std::string& packageName) { + sp<IAppOpsCallback> listener; { std::unique_lock lock{mCallbacksLock}; - if (!mCallbackRegistered.insert(packageName).second) { + auto& cb = mCallbackRegistered[packageName]; + if (cb) { return; } + cb = new AppOpsListener(*this, packageName); + listener = cb; } - /* TODO(b/152633648): restore callback after it's not crashing Binder anymore. - sp<AppOpsListener> listener = new AppOpsListener(*this, packageName); mAppOpsManager->startWatchingMode(AppOpsManager::OP_GET_USAGE_STATS, String16(packageName.c_str()), listener); - */ } -void IncrementalService::onAppOppChanged(const std::string& packageName) { +bool IncrementalService::unregisterAppOpsCallback(const std::string& packageName) { + sp<IAppOpsCallback> listener; + { + std::unique_lock lock{mCallbacksLock}; + auto found = mCallbackRegistered.find(packageName); + if (found == mCallbackRegistered.end()) { + return false; + } + listener = found->second; + mCallbackRegistered.erase(found); + } + + mAppOpsManager->stopWatchingMode(listener); + return true; +} + +void IncrementalService::onAppOpChanged(const std::string& packageName) { + if (!unregisterAppOpsCallback(packageName)) { + return; + } + std::vector<IfsMountPtr> affected; { std::lock_guard l(mLock); affected.reserve(mMounts.size()); for (auto&& [id, ifs] : mMounts) { - if (ifs->dataLoaderFilesystemParams.readLogsEnabled && ifs->dataLoaderParams.packageName == packageName) { + if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) { affected.push_back(ifs); } } } - /* TODO(b/152633648): restore callback after it's not crashing Kernel anymore. for (auto&& ifs : affected) { - applyStorageParams(*ifs); + applyStorageParams(*ifs, false); } - */ } -binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId, - int newStatus) { - if (externalListener) { +IncrementalService::DataLoaderStub::~DataLoaderStub() { + CHECK(mStatus == -1 || mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) + << "Dataloader has to be destroyed prior to destructor: " << mId + << ", status: " << mStatus; +} + +bool IncrementalService::DataLoaderStub::create() { + bool created = false; + auto status = mService.mDataLoaderManager->initializeDataLoader(mId, mParams, mControl, this, + &created); + if (!status.isOk() || !created) { + LOG(ERROR) << "Failed to create a data loader for mount " << mId; + return false; + } + return true; +} + +bool IncrementalService::DataLoaderStub::start() { + if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { + mStartRequested = true; + return true; + } + + sp<IDataLoader> dataloader; + auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader); + if (!status.isOk()) { + return false; + } + if (!dataloader) { + return false; + } + status = dataloader->start(mId); + if (!status.isOk()) { + return false; + } + return true; +} + +void IncrementalService::DataLoaderStub::destroy() { + mDestroyRequested = true; + mService.mDataLoaderManager->destroyDataLoader(mId); +} + +binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) { + if (mStatus == newStatus) { + return binder::Status::ok(); + } + + if (mListener) { // Give an external listener a chance to act before we destroy something. - externalListener->onStatusChanged(mountId, newStatus); + mListener->onStatusChanged(mountId, newStatus); } - bool startRequested = false; { - std::unique_lock l(incrementalService.mLock); - const auto& ifs = incrementalService.getIfsLocked(mountId); + std::unique_lock l(mService.mLock); + const auto& ifs = mService.getIfsLocked(mountId); if (!ifs) { LOG(WARNING) << "Received data loader status " << int(newStatus) << " for unknown mount " << mountId; return binder::Status::ok(); } - ifs->dataLoaderStatus = newStatus; + mStatus = newStatus; - if (newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) { - ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED; - incrementalService.deleteStorageLocked(*ifs, std::move(l)); + if (!mDestroyRequested && newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) { + mService.deleteStorageLocked(*ifs, std::move(l)); return binder::Status::ok(); } - - startRequested = ifs->dataLoaderStartRequested; } switch (newStatus) { case IDataLoaderStatusListener::DATA_LOADER_CREATED: { - if (startRequested) { - incrementalService.startDataLoader(mountId); + if (mStartRequested) { + start(); } break; } @@ -1378,8 +1472,14 @@ binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChange return binder::Status::ok(); } -void IncrementalService::AppOpsListener::opChanged(int32_t op, const String16&) { - incrementalService.onAppOppChanged(packageName); +void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) { + incrementalService.onAppOpChanged(packageName); +} + +binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams( + bool enableReadLogs, int32_t* _aidl_return) { + *_aidl_return = incrementalService.setStorageParams(storage, enableReadLogs); + return binder::Status::ok(); } } // namespace android::incremental diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index ff69633e185b..27d40f1506ca 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -39,8 +39,8 @@ #include "ServiceWrappers.h" #include "android/content/pm/BnDataLoaderStatusListener.h" +#include "android/os/incremental/BnIncrementalServiceConnector.h" #include "incfs.h" -#include "dataloader_ndk.h" #include "path.h" using namespace android::os::incremental; @@ -60,7 +60,8 @@ using Clock = std::chrono::steady_clock; using TimePoint = std::chrono::time_point<Clock>; using Seconds = std::chrono::seconds; -using DataLoaderStatusListener = ::android::sp<::android::content::pm::IDataLoaderStatusListener>; +using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener; +using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>; class IncrementalService final { public: @@ -95,7 +96,7 @@ public: void onDump(int fd); - std::optional<std::future<void>> onSystemReady(); + void onSystemReady(); StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams, const DataLoaderStatusListener& dataLoaderStatusListener, @@ -134,30 +135,69 @@ public: bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi); - class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener { + class AppOpsListener : public android::BnAppOpsCallback { public: - IncrementalDataLoaderListener(IncrementalService& incrementalService, - DataLoaderStatusListener externalListener) - : incrementalService(incrementalService), externalListener(externalListener) {} - // Callbacks interface - binder::Status onStatusChanged(MountId mount, int newStatus) override; + AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} + void opChanged(int32_t op, const String16& packageName) final; private: IncrementalService& incrementalService; - DataLoaderStatusListener externalListener; + const std::string packageName; }; - class AppOpsListener : public android::BnAppOpsCallback { + class IncrementalServiceConnector : public BnIncrementalServiceConnector { public: - AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} - void opChanged(int32_t op, const String16& packageName) override; + IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage) + : incrementalService(incrementalService), storage(storage) {} + binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final; private: IncrementalService& incrementalService; - const std::string packageName; + int32_t const storage; }; private: + static const bool sEnablePerfLogging; + + struct IncFsMount; + + class DataLoaderStub : public android::content::pm::BnDataLoaderStatusListener { + public: + DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params, + FileSystemControlParcel&& control, + const DataLoaderStatusListener* externalListener) + : mService(service), + mId(id), + mParams(std::move(params)), + mControl(std::move(control)), + mListener(externalListener ? *externalListener : DataLoaderStatusListener()) {} + ~DataLoaderStub(); + + bool create(); + bool start(); + void destroy(); + + // accessors + MountId id() const { return mId; } + const DataLoaderParamsParcel& params() const { return mParams; } + int status() const { return mStatus.load(); } + bool startRequested() const { return mStartRequested; } + + private: + binder::Status onStatusChanged(MountId mount, int newStatus) final; + + IncrementalService& mService; + MountId const mId; + DataLoaderParamsParcel const mParams; + FileSystemControlParcel const mControl; + DataLoaderStatusListener const mListener; + + std::atomic<int> mStatus = -1; + bool mStartRequested = false; + bool mDestroyRequested = false; + }; + using DataLoaderStubPtr = sp<DataLoaderStub>; + struct IncFsMount { struct Bind { StorageId storage; @@ -181,11 +221,8 @@ private: /*const*/ MountId mountId; StorageMap storages; BindMap bindPoints; - DataLoaderParamsParcel dataLoaderParams; - DataLoaderFilesystemParams dataLoaderFilesystemParams; + DataLoaderStubPtr dataLoaderStub; std::atomic<int> nextStorageDirNo{0}; - std::atomic<int> dataLoaderStatus = -1; - bool dataLoaderStartRequested = false; const IncrementalService& incrementalService; IncFsMount(std::string root, MountId mountId, Control control, @@ -193,9 +230,7 @@ private: : root(std::move(root)), control(std::move(control)), mountId(mountId), - incrementalService(incrementalService) { - dataLoaderFilesystemParams.readLogsEnabled = false; - } + incrementalService(incrementalService) {} IncFsMount(IncFsMount&&) = delete; IncFsMount& operator=(IncFsMount&&) = delete; ~IncFsMount(); @@ -222,8 +257,8 @@ private: std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock); - bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr); - bool startDataLoader(MountId mountId) const; + DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel&& params, + const DataLoaderStatusListener* externalListener = nullptr); BindPathMap::const_iterator findStorageLocked(std::string_view path) const; StorageId findStorageId(std::string_view path) const; @@ -231,13 +266,16 @@ private: void deleteStorage(IncFsMount& ifs); void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock); MountMap::iterator getStorageSlotLocked(); - std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage, + std::string normalizePathToStorage(const IfsMountPtr& incfs, StorageId storage, std::string_view path); + std::string normalizePathToStorageLocked(IncFsMount::StorageMap::iterator storageIt, + std::string_view path); - int applyStorageParams(IncFsMount& ifs); + binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs); void registerAppOpsCallback(const std::string& packageName); - void onAppOppChanged(const std::string& packageName); + bool unregisterAppOpsCallback(const std::string& packageName); + void onAppOpChanged(const std::string& packageName); // Member variables std::unique_ptr<VoldServiceWrapper> const mVold; @@ -252,11 +290,10 @@ private: BindPathMap mBindsByPath; std::mutex mCallbacksLock; - std::set<std::string> mCallbackRegistered; + std::map<std::string, sp<AppOpsListener>> mCallbackRegistered; std::atomic_bool mSystemReady = false; StorageId mNextId = 0; - std::promise<void> mPrepareDataLoaders; }; } // namespace android::incremental diff --git a/services/incremental/IncrementalServiceValidation.h b/services/incremental/IncrementalServiceValidation.h index 24f9f7f94dfd..48894c6926c8 100644 --- a/services/incremental/IncrementalServiceValidation.h +++ b/services/incremental/IncrementalServiceValidation.h @@ -41,7 +41,8 @@ inline int fromBinderStatus(const binder::Status& status) { : -EIO; } -inline binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation) { +inline binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation, + const char* package) { using android::base::StringPrintf; int32_t pid; @@ -52,23 +53,23 @@ inline binder::Status CheckPermissionForDataDelivery(const char* permission, con StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission)); } + String16 packageName{package}; + // Caller must also have op granted. PermissionController pc; - // Package is a required parameter. Need to obtain one. - Vector<String16> packages; - pc.getPackagesForUid(uid, packages); - if (packages.empty()) { + if (auto packageUid = pc.getPackageUid(packageName, 0); packageUid != uid) { return Exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d has no packages", uid, pid)); + StringPrintf("UID %d / PID %d does not own package %s", uid, pid, + package)); } - switch (auto result = pc.noteOp(String16(operation), uid, packages[0]); result) { + switch (auto result = pc.noteOp(String16(operation), uid, packageName); result) { case PermissionController::MODE_ALLOWED: case PermissionController::MODE_DEFAULT: return binder::Status::ok(); default: return Exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks app-op %s, error %d", uid, pid, - operation, result)); + StringPrintf("UID %d / PID %d / package %s lacks app-op %s, error %d", + uid, pid, package, operation, result)); } } diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index 449b457045c6..84bf1ffaf45c 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -16,6 +16,8 @@ #pragma once +#include "IncrementalServiceValidation.h" + #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <android/content/pm/DataLoaderParamsParcel.h> @@ -85,7 +87,10 @@ public: class AppOpsManagerWrapper { public: virtual ~AppOpsManagerWrapper() = default; + virtual binder::Status checkPermission(const char* permission, const char* operation, + const char* package) const = 0; virtual void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) = 0; + virtual void stopWatchingMode(const sp<IAppOpsCallback>& callback) = 0; }; class ServiceManagerWrapper { @@ -105,17 +110,19 @@ public: ~RealVoldService() = default; binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir, int32_t flags, - IncrementalFileSystemControlParcel* _aidl_return) const override { + IncrementalFileSystemControlParcel* _aidl_return) const final { return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return); } - binder::Status unmountIncFs(const std::string& dir) const override { + binder::Status unmountIncFs(const std::string& dir) const final { return mInterface->unmountIncFs(dir); } binder::Status bindMount(const std::string& sourceDir, - const std::string& targetDir) const override { + const std::string& targetDir) const final { return mInterface->bindMount(sourceDir, targetDir); } - binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const override { + binder::Status setIncFsMountOptions( + const ::android::os::incremental::IncrementalFileSystemControlParcel& control, + bool enableReadLogs) const final { return mInterface->setIncFsMountOptions(control, enableReadLogs); } @@ -131,13 +138,13 @@ public: binder::Status initializeDataLoader(MountId mountId, const DataLoaderParamsParcel& params, const FileSystemControlParcel& control, const sp<IDataLoaderStatusListener>& listener, - bool* _aidl_return) const override { + bool* _aidl_return) const final { return mInterface->initializeDataLoader(mountId, params, control, listener, _aidl_return); } - binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const override { + binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const final { return mInterface->getDataLoader(mountId, _aidl_return); } - binder::Status destroyDataLoader(MountId mountId) const override { + binder::Status destroyDataLoader(MountId mountId) const final { return mInterface->destroyDataLoader(mountId); } @@ -148,9 +155,18 @@ private: class RealAppOpsManager : public AppOpsManagerWrapper { public: ~RealAppOpsManager() = default; - void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) override { + binder::Status checkPermission(const char* permission, const char* operation, + const char* package) const final { + return android::incremental::CheckPermissionForDataDelivery(permission, operation, package); + } + void startWatchingMode(int32_t op, const String16& packageName, + const sp<IAppOpsCallback>& callback) final { mAppOpsManager.startWatchingMode(op, packageName, callback); } + void stopWatchingMode(const sp<IAppOpsCallback>& callback) final { + mAppOpsManager.stopWatchingMode(callback); + } + private: android::AppOpsManager mAppOpsManager; }; @@ -174,36 +190,35 @@ class RealIncFs : public IncFsWrapper { public: RealIncFs() = default; ~RealIncFs() = default; - Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const override { + Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const final { return incfs::createControl(cmd, pendingReads, logs); } ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id, - NewFileParams params) const override { + NewFileParams params) const final { return incfs::makeFile(control, path, mode, id, params); } - ErrorCode makeDir(const Control& control, std::string_view path, int mode) const override { + ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final { return incfs::makeDir(control, path, mode); } - RawMetadata getMetadata(const Control& control, FileId fileid) const override { + RawMetadata getMetadata(const Control& control, FileId fileid) const final { return incfs::getMetadata(control, fileid); } - RawMetadata getMetadata(const Control& control, std::string_view path) const override { + RawMetadata getMetadata(const Control& control, std::string_view path) const final { return incfs::getMetadata(control, path); } - FileId getFileId(const Control& control, std::string_view path) const override { + FileId getFileId(const Control& control, std::string_view path) const final { return incfs::getFileId(control, path); } - ErrorCode link(const Control& control, std::string_view from, - std::string_view to) const override { + ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final { return incfs::link(control, from, to); } - ErrorCode unlink(const Control& control, std::string_view path) const override { + ErrorCode unlink(const Control& control, std::string_view path) const final { return incfs::unlink(control, path); } - base::unique_fd openForSpecialOps(const Control& control, FileId id) const override { + base::unique_fd openForSpecialOps(const Control& control, FileId id) const final { return base::unique_fd{incfs::openForSpecialOps(control, id).release()}; } - ErrorCode writeBlocks(Span<const DataBlock> blocks) const override { + ErrorCode writeBlocks(Span<const DataBlock> blocks) const final { return incfs::writeBlocks(blocks); } }; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 5553f688060a..991131950531 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -131,44 +131,60 @@ public: binder::Status(int32_t mountId, sp<IDataLoader>* _aidl_return)); MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId)); + void initializeDataLoaderSuccess() { + ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk)); + } + void initializeDataLoaderFails() { + ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) + .WillByDefault(Return( + (binder::Status::fromExceptionCode(1, String8("failed to prepare"))))); + } + void getDataLoaderSuccess() { + ON_CALL(*this, getDataLoader(_, _)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk)); + } + void destroyDataLoaderOk() { + ON_CALL(*this, destroyDataLoader(_)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::setDataLoaderStatusDestroyed)); + } binder::Status initializeDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params, const FileSystemControlParcel& control, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { mId = mountId; mListener = listener; + mServiceConnector = control.service; *_aidl_return = true; return binder::Status::ok(); } - binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) { *_aidl_return = mDataLoader; return binder::Status::ok(); } - - void initializeDataLoaderFails() { - ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) - .WillByDefault(Return( - (binder::Status::fromExceptionCode(1, String8("failed to prepare"))))); - } - void initializeDataLoaderSuccess() { - ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) - .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk)); - } - void getDataLoaderSuccess() { - ON_CALL(*this, getDataLoader(_, _)) - .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk)); - } void setDataLoaderStatusNotReady() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); } void setDataLoaderStatusReady() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED); } + binder::Status setDataLoaderStatusDestroyed(int32_t id) { + if (mListener) { + mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); + } + return binder::Status::ok(); + } + int32_t setStorageParams(bool enableReadLogs) { + int32_t result = -1; + EXPECT_NE(mServiceConnector.get(), nullptr); + EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk()); + return result; + } private: int mId; sp<IDataLoaderStatusListener> mListener; + sp<IIncrementalServiceConnector> mServiceConnector; sp<IDataLoader> mDataLoader = sp<IDataLoader>(new FakeDataLoader()); }; @@ -221,7 +237,28 @@ public: }; class MockAppOpsManager : public AppOpsManagerWrapper { +public: + MOCK_CONST_METHOD3(checkPermission, binder::Status(const char*, const char*, const char*)); MOCK_METHOD3(startWatchingMode, void(int32_t, const String16&, const sp<IAppOpsCallback>&)); + MOCK_METHOD1(stopWatchingMode, void(const sp<IAppOpsCallback>&)); + + void checkPermissionSuccess() { + ON_CALL(*this, checkPermission(_, _, _)).WillByDefault(Return(android::incremental::Ok())); + } + void checkPermissionFails() { + ON_CALL(*this, checkPermission(_, _, _)) + .WillByDefault( + Return(android::incremental::Exception(binder::Status::EX_SECURITY, {}))); + } + void initializeStartWatchingMode() { + ON_CALL(*this, startWatchingMode(_, _, _)) + .WillByDefault(Invoke(this, &MockAppOpsManager::storeCallback)); + } + void storeCallback(int32_t, const String16&, const sp<IAppOpsCallback>& cb) { + mStoredCallback = cb; + } + + sp<IAppOpsCallback> mStoredCallback; }; class MockServiceManager : public ServiceManagerWrapper { @@ -269,6 +306,7 @@ public: mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; + mDataLoaderManager->destroyDataLoaderOk(); mIncrementalService->onSystemReady(); } @@ -316,6 +354,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) { mVold->mountIncFsInvalidControlParcel(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, @@ -327,7 +366,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { mVold->mountIncFsSuccess(); mIncFs->makeFileFails(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; int storageId = @@ -341,7 +380,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { mIncFs->makeFileSuccess(); mVold->bindMountFails(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; int storageId = @@ -355,7 +394,7 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderFails(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = @@ -369,7 +408,7 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = @@ -418,15 +457,71 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { mVold->setIncFsMountOptionsSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); mDataLoaderManager->getDataLoaderSuccess(); + mAppOpsManager->checkPermissionSuccess(); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + // We are calling setIncFsMountOptions(true). + EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(1); + // After setIncFsMountOptions succeeded expecting to start watching. + EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1); + // Not expecting callback removal. + EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0); +} + +TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mVold->setIncFsMountOptionsSuccess(); + mDataLoaderManager->initializeDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + mAppOpsManager->checkPermissionSuccess(); + mAppOpsManager->initializeStartWatchingMode(); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + // We are calling setIncFsMountOptions(true). + EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(1); + // setIncFsMountOptions(false) is called on the callback. + EXPECT_CALL(*mVold, setIncFsMountOptions(_, false)).Times(1); + // After setIncFsMountOptions succeeded expecting to start watching. + EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1); + // After callback is called, disable read logs and remove callback. + EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(1); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0); + ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get()); + mAppOpsManager->mStoredCallback->opChanged(0, {}); +} + +TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mDataLoaderManager->initializeDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + mAppOpsManager->checkPermissionFails(); EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); - EXPECT_CALL(*mVold, setIncFsMountOptions(_, _)); + // checkPermission fails, no calls to set opitions, start or stop WatchingMode. + EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(0); + EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0); + EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); - ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0); + ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0); } TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { @@ -436,15 +531,20 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { mVold->setIncFsMountOptionsFails(); mDataLoaderManager->initializeDataLoaderSuccess(); mDataLoaderManager->getDataLoaderSuccess(); + mAppOpsManager->checkPermissionSuccess(); EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); - EXPECT_CALL(*mVold, setIncFsMountOptions(_, _)); + // We are calling setIncFsMountOptions. + EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(1); + // setIncFsMountOptions fails, no calls to start or stop WatchingMode. + EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0); + EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); - ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0); + ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0); } TEST_F(IncrementalServiceTest, testMakeDirectory) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2a914ecf4db6..e2a247394a81 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.pm.OtaDexoptService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; +import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.LegacyRoleResolutionPolicy; @@ -519,10 +520,8 @@ public final class SystemServer { // Initialize native services. System.loadLibrary("android_servers"); - // Debug builds - allow heap profiling. - if (Build.IS_DEBUGGABLE) { - initZygoteChildHeapProfiling(); - } + // Allow heap / perf profiling. + initZygoteChildHeapProfiling(); // Debug builds - spawn a thread to monitor for fd leaks. if (Build.IS_DEBUGGABLE) { @@ -839,6 +838,11 @@ public final class SystemServer { Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain"); } + // Now that the package manager has started, register the dex load reporter to capture any + // dex files loaded by system server. + // These dex files will be optimized by the BackgroundDexOptService. + SystemServerDexLoadReporter.configureSystemServerDexReporter(mPackageManagerService); + mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); t.traceEnd(); diff --git a/services/net/Android.bp b/services/net/Android.bp index c54102fb1d3d..3eba6c4df1bf 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -13,7 +13,7 @@ java_library_static { ], static_libs: [ "dnsresolver_aidl_interface-V2-java", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-client", "net-utils-services-common", @@ -44,7 +44,7 @@ java_library { ], static_libs: [ "dnsresolver_aidl_interface-V2-java", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-client", "net-utils-services-common", diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index 138f9829c088..f8d197acf883 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -199,6 +199,12 @@ public class CrossProfileAppsServiceImplRoboTest { CROSS_PROFILE_APP_PACKAGE_NAME, PERSONAL_PROFILE_UID, PERSONAL_PROFILE_USER_ID); ShadowApplicationPackageManager.setPackageUidAsUser( CROSS_PROFILE_APP_PACKAGE_NAME, WORK_PROFILE_UID, WORK_PROFILE_USER_ID); + when(mPackageManagerInternal.getPackageUidInternal( + CROSS_PROFILE_APP_PACKAGE_NAME, /* flags= */ 0, PERSONAL_PROFILE_USER_ID)) + .thenReturn(PERSONAL_PROFILE_UID); + when(mPackageManagerInternal.getPackageUidInternal( + CROSS_PROFILE_APP_PACKAGE_NAME, /* flags= */ 0, WORK_PROFILE_USER_ID)) + .thenReturn(WORK_PROFILE_UID); } @Before @@ -456,6 +462,19 @@ public class CrossProfileAppsServiceImplRoboTest { } @Test + public void canUserAttemptToConfigureInteractAcrossProfiles_platformSignedAppWithAutomaticPermission_returnsFalse() { + mockCrossProfileAppNotWhitelistedByOem(); + shadowOf(mContext).grantPermissions( + Process.myPid(), + PERSONAL_PROFILE_UID, + Manifest.permission.INTERACT_ACROSS_PROFILES); + + assertThat(mCrossProfileAppsServiceImpl + .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isFalse(); + } + + @Test public void canUserAttemptToConfigureInteractAcrossProfiles_returnsTrue() { assertThat(mCrossProfileAppsServiceImpl .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) @@ -528,6 +547,11 @@ public class CrossProfileAppsServiceImplRoboTest { .thenReturn(new ArrayList<>()); } + private void mockCrossProfileAppNotWhitelistedByOem() { + when(mDevicePolicyManagerInternal.getDefaultCrossProfilePackages()) + .thenReturn(new ArrayList<>()); + } + private boolean receivedManifestCanInteractAcrossProfilesChangedBroadcast() { final UserHandle userHandle = UserHandle.of(PERSONAL_PROFILE_USER_ID); if (!mSentUserBroadcasts.containsKey(userHandle)) { diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java index c225d3feb063..1aab6722dfee 100644 --- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java +++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java @@ -16,6 +16,8 @@ package com.android.server.systemcaptions; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -37,6 +39,8 @@ final class RemoteSystemCaptionsManagerService { private static final String SERVICE_INTERFACE = "android.service.systemcaptions.SystemCaptionsManagerService"; + private static final int MSG_BIND = 1; + private final Object mLock = new Object(); private final Context mContext; @@ -71,18 +75,26 @@ final class RemoteSystemCaptionsManagerService { if (mVerbose) { Slog.v(TAG, "initialize()"); } - ensureBound(); + scheduleBind(); + } + + /** + * Destroys this service. + */ + public void destroy() { + mHandler.sendMessage( + obtainMessage(RemoteSystemCaptionsManagerService::handleDestroy, this)); } - void destroy() { + void handleDestroy() { if (mVerbose) { - Slog.v(TAG, "destroy()"); + Slog.v(TAG, "handleDestroy()"); } synchronized (mLock) { if (mDestroyed) { if (mVerbose) { - Slog.v(TAG, "destroy(): Already destroyed"); + Slog.v(TAG, "handleDestroy(): Already destroyed"); } return; } @@ -97,14 +109,24 @@ final class RemoteSystemCaptionsManagerService { } } - private void ensureBound() { + private void scheduleBind() { + if (mHandler.hasMessages(MSG_BIND)) { + if (mVerbose) Slog.v(TAG, "scheduleBind(): already scheduled"); + return; + } + mHandler.sendMessage( + obtainMessage(RemoteSystemCaptionsManagerService::handleEnsureBound, this) + .setWhat(MSG_BIND)); + } + + private void handleEnsureBound() { synchronized (mLock) { if (mService != null || mBinding) { return; } if (mVerbose) { - Slog.v(TAG, "ensureBound(): binding"); + Slog.v(TAG, "handleEnsureBound(): binding"); } mBinding = true; diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml index d30f479195e3..395b8ab4ba75 100644 --- a/services/tests/servicestests/res/raw/comp_policies_primary.xml +++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml @@ -3,6 +3,11 @@ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> <policies flags="991"/> <password-history-length value="33" /> + <require_auto_time value="true" /> <user-restrictions no_bluetooth="true" /> + <disable-screen-capture value="true" /> + <disable-account-management> + <account-type value="com.google-primary" /> + </disable-account-management> </admin> </policies> diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml index c874dcca2c73..c65d05693f10 100644 --- a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml +++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml @@ -2,5 +2,8 @@ <policies setup-complete="true" provisioning-state="3"> <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> <policies flags="991"/> + <disable-account-management> + <account-type value="com.google-profile" /> + </disable-account-management> </admin> </policies> 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 7bf1d98d9a3f..0445bff8fd0d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -16,7 +16,10 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY; +import static android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS; import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS; import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION; import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES; import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS; @@ -69,6 +72,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.graphics.Region; +import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -93,6 +97,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -162,6 +167,7 @@ public class AbstractAccessibilityServiceConnectionTest { @Mock private IAccessibilityInteractionConnectionCallback mMockCallback; @Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher; @Mock private MagnificationController mMockMagnificationController; + @Mock private RemoteCallback.OnResultListener mMockListener; @Before public void setup() { @@ -705,6 +711,38 @@ public class AbstractAccessibilityServiceConnectionTest { })); } + @Test + public void takeScreenshot_NoA11yAccess_returnErrorCode() throws InterruptedException { + // no checkAccessibilityAccess, should return error code. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); + + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, + new RemoteCallback(mMockListener)); + mHandler.sendLastMessage(); + + verify(mMockListener).onResult(Mockito.argThat( + bundle -> ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS + == bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS))); + } + + @Test + public void takeScreenshot_invalidDisplay_returnErrorCode() throws InterruptedException { + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true); + + final DisplayManager displayManager = new DisplayManager(mMockContext); + when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(displayManager); + + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY + 1, + new RemoteCallback(mMockListener)); + mHandler.sendLastMessage(); + + verify(mMockListener).onResult(Mockito.argThat( + bundle -> ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY + == bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS))); + } + private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, int feedbackType, int flags, String[] packageNames, int notificationTimeout) { serviceInfo.eventTypes = eventType; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index de2addffa2c5..74e7f8c44d1a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -19,6 +19,7 @@ import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile; +import static org.junit.Assert.assertArrayEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -378,6 +379,15 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { 33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1)); assertEquals("Password history policy was put into non-parent PO instance", 0, dpm.getPasswordHistoryLength(admin1)); + assertTrue("Screen capture restriction wasn't migrated to PO parent instance", + dpm.getParentProfileInstance(admin1).getScreenCaptureDisabled(admin1)); + + assertArrayEquals("Accounts with management disabled weren't migrated to PO parent", + new String[] {"com.google-primary"}, + dpm.getParentProfileInstance(admin1).getAccountTypesWithManagementDisabled()); + assertArrayEquals("Accounts with management disabled for profile were lost", + new String[] {"com.google-profile"}, + dpm.getAccountTypesWithManagementDisabled()); assertTrue("User restriction wasn't migrated to PO parent instance", dpm.getParentProfileInstance(admin1).getUserRestrictions(admin1) @@ -385,7 +395,15 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { assertFalse("User restriction was put into non-parent PO instance", dpm.getUserRestrictions(admin1).containsKey(UserManager.DISALLOW_BLUETOOTH)); - // TODO(b/143516163): verify more policies. + assertTrue("User restriction wasn't migrated to PO parent instance", + dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID) + .getParentActiveAdmin() + .getEffectiveRestrictions() + .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME)); + assertFalse("User restriction was put into non-parent PO instance", + dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID) + .getEffectiveRestrictions() + .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME)); }); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index fe47cea6b693..fe224ce058f4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -83,7 +83,6 @@ import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.os.UserManagerInternal; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.security.KeyChain; @@ -1170,7 +1169,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.clearDeviceOwnerApp(admin1.getPackageName())); when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true); - reset(getServices().userManagerInternal); dpm.clearDeviceOwnerApp(admin1.getPackageName()); // Now DO shouldn't be set. @@ -1181,9 +1179,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( - eq(UserHandle.USER_SYSTEM), - MockUtils.checkUserRestrictions(), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); verify(getServices().usageStatsManagerInternal).setActiveAdminApps( null, UserHandle.USER_SYSTEM); @@ -1745,15 +1742,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS, - UserManager.DISALLOW_ADD_USER), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER), + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM, + UserManager.DISALLOW_OUTGOING_CALLS), + eq(true)); reset(getServices().userManagerInternal); DpmTestUtils.assertRestrictions( @@ -1770,8 +1768,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM, + UserManager.DISALLOW_OUTGOING_CALLS), + eq(true)); reset(getServices().userManagerInternal); DpmTestUtils.assertRestrictions( @@ -1787,7 +1787,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); assertNoDeviceOwnerRestrictions(); @@ -1801,7 +1801,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME, UserManager.DISALLOW_UNMUTE_MICROPHONE), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME); @@ -1813,7 +1813,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN); @@ -1821,7 +1821,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN, UserManager.DISALLOW_ADD_USER), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); dpm.setCameraDisabled(admin1, true); @@ -1830,7 +1830,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // DISALLOW_CAMERA will be applied globally. MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN, UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_CAMERA), - eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); reset(getServices().userManagerInternal); } @@ -1887,17 +1887,19 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); - reset(getServices().userManagerInternal); + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES), + eq(false)); dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, UserManager.DISALLOW_OUTGOING_CALLS), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); - reset(getServices().userManagerInternal); + eq(false)); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions( @@ -1918,9 +1920,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); - reset(getServices().userManagerInternal); + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE, + UserManager.DISALLOW_OUTGOING_CALLS), + eq(false)); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions( @@ -1940,8 +1943,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), MockUtils.checkUserRestrictions(), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); - reset(getServices().userManagerInternal); + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE), eq(false)); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions(), @@ -1956,21 +1958,25 @@ public class DevicePolicyManagerTest extends DpmTestBase { // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even // though when DO sets them they'll be applied globally. dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME); - reset(getServices().userManagerInternal); + dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME, + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE, + UserManager.DISALLOW_ADJUST_VOLUME, UserManager.DISALLOW_UNMUTE_MICROPHONE), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); - reset(getServices().userManagerInternal); + eq(false)); dpm.setCameraDisabled(admin1, true); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, UserManager.DISALLOW_CAMERA), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER)); + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE, + UserManager.DISALLOW_ADJUST_VOLUME, + UserManager.DISALLOW_UNMUTE_MICROPHONE, + UserManager.DISALLOW_CAMERA), + eq(false)); reset(getServices().userManagerInternal); // TODO Make sure restrictions are written to the file. @@ -2004,15 +2010,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { ); public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { - final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE; final int MANAGED_PROFILE_ADMIN_UID = - UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); + UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); - when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID)) + when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE)) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) { @@ -2021,16 +2026,24 @@ public class DevicePolicyManagerTest extends DpmTestBase { parentDpm.setCameraDisabled(admin1, true); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( - eq(MANAGED_PROFILE_USER_ID), + eq(DpmMockContext.CALLER_USER_HANDLE), MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); - reset(getServices().userManagerInternal); + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE), + eq(false)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(UserManager.DISALLOW_CAMERA), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .getParentActiveAdmin() + .getEffectiveRestrictions() + ); parentDpm.setCameraDisabled(admin1, false); - verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( - eq(MANAGED_PROFILE_USER_ID), - MockUtils.checkUserRestrictions(), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .getParentActiveAdmin() + .getEffectiveRestrictions() + ); reset(getServices().userManagerInternal); } @@ -2039,13 +2052,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), MockUtils.checkUserRestrictions(restriction), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); + MockUtils.checkUserRestrictions(DpmMockContext.CALLER_USER_HANDLE), + eq(false)); parentDpm.clearUserRestriction(admin1, restriction); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions(), - parentDpm.getUserRestrictions(admin1) + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .getParentActiveAdmin() + .getEffectiveRestrictions() ); - reset(getServices().userManagerInternal); } public void testNoDefaultEnabledUserRestrictions() throws Exception { @@ -2079,11 +2094,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void assertNoDeviceOwnerRestrictions() { DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions(), - getDeviceOwner().ensureUserRestrictions() - ); - DpmTestUtils.assertRestrictions( - DpmTestUtils.newRestrictions(), - dpm.getUserRestrictions(admin1) + getDeviceOwner().getEffectiveRestrictions() ); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java index 09a681913e45..15f3ed1be552 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java @@ -15,10 +15,6 @@ */ package com.android.server.devicepolicy; -import com.google.common.base.Objects; - -import com.android.server.pm.UserRestrictionsUtils; - import android.content.ComponentName; import android.content.Intent; import android.os.BaseBundle; @@ -26,6 +22,11 @@ import android.os.Bundle; import android.os.UserHandle; import android.util.ArraySet; +import com.android.server.pm.RestrictionsSet; +import com.android.server.pm.UserRestrictionsUtils; + +import com.google.common.base.Objects; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -106,11 +107,14 @@ public class MockUtils { } public static Bundle checkUserRestrictions(String... keys) { - final Bundle expected = DpmTestUtils.newRestrictions(java.util.Objects.requireNonNull(keys)); + final Bundle expected = DpmTestUtils.newRestrictions( + java.util.Objects.requireNonNull(keys)); final Matcher<Bundle> m = new BaseMatcher<Bundle>() { @Override public boolean matches(Object item) { - if (item == null) return false; + if (item == null) { + return false; + } return UserRestrictionsUtils.areEqual((Bundle) item, expected); } @@ -122,6 +126,26 @@ public class MockUtils { return MockitoHamcrest.argThat(m); } + public static RestrictionsSet checkUserRestrictions(int userId, String... keys) { + final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId, + java.util.Objects.requireNonNull(keys)); + final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() { + @Override + public boolean matches(Object item) { + if (item == null) return false; + RestrictionsSet actual = (RestrictionsSet) item; + return UserRestrictionsUtils.areEqual(expected.getRestrictions(userId), + actual.getRestrictions(userId)); + } + + @Override + public void describeTo(Description description) { + description.appendText("User restrictions=" + getRestrictionsAsString(expected)); + } + }; + return MockitoHamcrest.argThat(m); + } + public static Set<String> checkApps(String... adminApps) { final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() { @Override @@ -146,6 +170,23 @@ public class MockUtils { return MockitoHamcrest.argThat(m); } + private static String getRestrictionsAsString(RestrictionsSet r) { + final StringBuilder sb = new StringBuilder(); + sb.append("{"); + + if (r != null) { + String sep = ""; + for (int i = 0; i < r.size(); i++) { + sb.append(sep); + sep = ","; + sb.append( + String.format("%s= %s", r.keyAt(i), getRestrictionsAsString(r.valueAt(i)))); + } + } + sb.append("}"); + return sb.toString(); + } + private static String getRestrictionsAsString(Bundle b) { final StringBuilder sb = new StringBuilder(); sb.append("["); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 234c987bd3d6..7b3417a67857 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -60,6 +60,7 @@ public class AutomaticBrightnessControllerTest { @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; @Mock Handler mNoopHandler; + @Mock DisplayDeviceConfig mDisplayDeviceConfig; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; @Before @@ -82,7 +83,8 @@ public class AutomaticBrightnessControllerTest { BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, - mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext); + mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext, + mDisplayDeviceConfig); controller.setLoggingEnabled(true); // Configure the brightness controller and grab an instance of the sensor listener, diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java new file mode 100644 index 000000000000..301a9fe64d5e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.graphics.Point; +import android.view.DisplayInfo; +import android.view.Surface; +import android.view.SurfaceControl; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; + +public class LogicalDisplayTest { + private static final int DISPLAY_ID = 0; + private static final int LAYER_STACK = 0; + private static final int DISPLAY_WIDTH = 100; + private static final int DISPLAY_HEIGHT = 200; + + private LogicalDisplay mLogicalDisplay; + private DisplayDevice mDisplayDevice; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + mDisplayDevice = mock(DisplayDevice.class); + DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); + displayDeviceInfo.width = DISPLAY_WIDTH; + displayDeviceInfo.height = DISPLAY_HEIGHT; + displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); + when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo); + + ArrayList<DisplayDevice> displayDevices = new ArrayList<>(); + displayDevices.add(mDisplayDevice); + mLogicalDisplay.updateLocked(displayDevices); + } + + @Test + public void testGetDisplayPosition() { + Point expectedPosition = new Point(); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(20, 40); + mLogicalDisplay.setDisplayOffsetsLocked(20, 40); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(40, -20); + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_HEIGHT; + displayInfo.logicalHeight = DISPLAY_WIDTH; + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java index 7c9a81d2e094..a525814435ea 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java @@ -43,7 +43,9 @@ import java.lang.ref.WeakReference; @RunWith(AndroidJUnit4.class) public class AppSaturationControllerTest { - private static final String TEST_PACKAGE_NAME = "com.android.test"; + private static final String TEST_CALLER_PACKAGE_NAME = "com.android.test.caller"; + private static final String TEST_CALLER_PACKAGE_NAME_TWO = "com.android.test.caller.two"; + private static final String TEST_AFFECTED_PACKAGE_NAME = "com.android.test.affected"; private int mUserId; private AppSaturationController mAppSaturationController; @@ -70,8 +72,11 @@ public class AppSaturationControllerTest { public void addColorTransformController_appliesExistingSaturation() { final WeakReference<ColorTransformController> ref = new WeakReference<>( mColorTransformController); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30); - mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 30); + mAppSaturationController + .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref); AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix); verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); } @@ -80,14 +85,19 @@ public class AppSaturationControllerTest { public void setSaturationLevel_resetToDefault() { final WeakReference<ColorTransformController> ref = new WeakReference<>( mColorTransformController); - mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref); + mAppSaturationController + .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref); verify(mColorTransformController, never()) .applyAppSaturation(any(), eq(TRANSLATION_VECTOR)); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 30); AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix); verify(mColorTransformController, times(1)) .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 100); AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix); verify(mColorTransformController, times(2)) .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); @@ -97,19 +107,76 @@ public class AppSaturationControllerTest { public void setSaturationLevel_updateLevel() { final WeakReference<ColorTransformController> ref = new WeakReference<>( mColorTransformController); - mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref); + mAppSaturationController + .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref); verify(mColorTransformController, never()) .applyAppSaturation(any(), eq(TRANSLATION_VECTOR)); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 30); AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix); verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 70); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 70); AppSaturationController.computeGrayscaleTransformMatrix(.7f, mMatrix); verify(mColorTransformController, times(2)) .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); - mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 100); AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix); verify(mColorTransformController, times(3)) .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); } + + @Test + public void setSaturationLevel_multipleCallers_appliesStrongest() { + final WeakReference<ColorTransformController> ref = new WeakReference<>( + mColorTransformController); + mAppSaturationController + .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref); + verify(mColorTransformController, never()) + .applyAppSaturation(any(), eq(TRANSLATION_VECTOR)); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 30); + AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix); + verify(mColorTransformController, times(1)) + .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME, + mUserId, + 70); + verify(mColorTransformController, times(2)) + .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); + } + + @Test + public void setSaturationLevel_multipleCallers_removingOneDoesNotAffectTheOther() { + final WeakReference<ColorTransformController> ref = new WeakReference<>( + mColorTransformController); + mAppSaturationController + .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref); + verify(mColorTransformController, never()) + .applyAppSaturation(any(), eq(TRANSLATION_VECTOR)); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId, + 70); + AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix); + verify(mColorTransformController, times(1)) + .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME, + mUserId, + 30); + AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix); + verify(mColorTransformController, times(2)) + .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR)); + mAppSaturationController + .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME, + mUserId, + 100); + AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix); + } } diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java index ccbaee41af7c..aa923e22444d 100644 --- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java @@ -63,6 +63,16 @@ public class LightsServiceTest { fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2) }; } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } }; private static HwLight fakeHwLight(int id, int type, int ordinal) { diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 820e61cb0a08..9eda718ed922 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -377,8 +377,7 @@ class OverlayManagerServiceImplTestsBase { return false; } final String key = createKey(overlayPackage.packageName, userId); - mIdmapFiles.add(key); - return true; + return mIdmapFiles.add(key); } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 5109de501157..b60e99363706 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -17,7 +17,9 @@ package com.android.server.pm; -import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -48,7 +50,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; -import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,7 +59,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.security.cert.CertificateException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -650,32 +651,32 @@ public class AppsFilterTest { final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1; final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2; PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId); - PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("some.system.pkg"), + PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"), seesNothingAppId); PackageSetting hasProvider = simulateAddPackage(appsFilter, - pkgWithProvider("com.some.package", "com.some.authority"), hasProviderAppId); + pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId); PackageSetting queriesProvider = simulateAddPackage(appsFilter, - pkgQueriesProvider("com.some.other.package", "com.some.authority"), + pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"), queriesProviderAppId); final int[] systemFilter = appsFilter.getVisibilityWhitelist(system, new int[]{0}, mExisting).get(0); - assertThat(Arrays.asList(systemFilter), arrayContaining(systemAppId)); + assertThat(toList(systemFilter), empty()); final int[] seesNothingFilter = appsFilter.getVisibilityWhitelist(seesNothing, new int[]{0}, mExisting).get(0); - assertThat(Arrays.asList(seesNothingFilter), - arrayContaining(systemAppId, seesNothingAppId)); + assertThat(toList(seesNothingFilter), + contains(seesNothingAppId)); final int[] hasProviderFilter = appsFilter.getVisibilityWhitelist(hasProvider, new int[]{0}, mExisting).get(0); - assertThat(Arrays.asList(hasProviderFilter), - arrayContaining(systemAppId, hasProviderAppId, queriesProviderAppId)); + assertThat(toList(hasProviderFilter), + contains(hasProviderAppId, queriesProviderAppId)); int[] queriesProviderFilter = appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); - assertThat(Arrays.asList(queriesProviderFilter), - arrayContaining(systemAppId, queriesProviderAppId)); + assertThat(toList(queriesProviderFilter), + contains(queriesProviderAppId)); // provider read appsFilter.grantImplicitAccess(hasProviderAppId, queriesProviderAppId); @@ -683,11 +684,16 @@ public class AppsFilterTest { // ensure implicit access is included in the filter queriesProviderFilter = appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); - assertThat(Arrays.asList(queriesProviderFilter), - arrayContaining(systemAppId, hasProviderAppId, queriesProviderAppId)); + assertThat(toList(queriesProviderFilter), + contains(hasProviderAppId, queriesProviderAppId)); } - private void assertThat(List<int[]> asList, Matcher<Integer[]> arrayContainingInAnyOrder) { + private List<Integer> toList(int[] array) { + ArrayList<Integer> ret = new ArrayList<>(array.length); + for (int i = 0; i < array.length; i++) { + ret.add(i, array[i]); + } + return ret; } private interface WithSettingBuilder { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java index 1c2313e3e32f..dc181a959d83 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java @@ -22,7 +22,6 @@ import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; -import android.os.UserManagerInternal; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseArray; @@ -118,135 +117,6 @@ public class UserRestrictionsUtilsTest extends AndroidTestCase { UserManager.DISALLOW_ADJUST_VOLUME, user)); } - public void testSortToGlobalAndLocal() { - final Bundle local = new Bundle(); - final Bundle global = new Bundle(); - - UserRestrictionsUtils.sortToGlobalAndLocal(null, - UserManagerInternal.OWNER_TYPE_PROFILE_OWNER, - global, local); - assertEquals(0, global.size()); - assertEquals(0, local.size()); - - UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, - UserManagerInternal.OWNER_TYPE_PROFILE_OWNER, - global, local); - assertEquals(0, global.size()); - assertEquals(0, local.size()); - - // Restrictions set by DO. - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions( - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL, - UserManager.ENSURE_VERIFY_APPS, - UserManager.DISALLOW_CAMERA - ), UserManagerInternal.OWNER_TYPE_DEVICE_OWNER, - global, local); - - - assertRestrictions(newRestrictions( - // This one is global no matter who sets it. - UserManager.ENSURE_VERIFY_APPS, - - // These can be set by PO too, but when DO sets them, they're global. - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - - // These can only be set by DO. - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING, - - // This can be set by DO or PO of organisation owned device - UserManager.DISALLOW_CAMERA - ), global); - - assertRestrictions(newRestrictions( - // They can be set by both DO/PO. - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL - ), local); - - local.clear(); - global.clear(); - - // Restrictions set by PO. - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions( - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL, - UserManager.ENSURE_VERIFY_APPS, - UserManager.DISALLOW_CAMERA - ), UserManagerInternal.OWNER_TYPE_PROFILE_OWNER, - global, local); - - assertRestrictions(newRestrictions( - // This one is global no matter who sets it. - UserManager.ENSURE_VERIFY_APPS - ), global); - - assertRestrictions(newRestrictions( - // These can be set by PO too, but when PO sets them, they're local. - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - - // They can be set by both DO/PO. - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL, - - // These can only be set by DO. - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING, - - // This can be set by DO or PO of organisation owned device - UserManager.DISALLOW_CAMERA - ), local); - - local.clear(); - global.clear(); - - // Restrictions set by PO of organisation owned device - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions( - UserManager.DISALLOW_CONFIG_DATE_TIME - ), UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, - global, local); - - assertRestrictions(newRestrictions( - // This user restriction is global when set by PO of org owned device - UserManager.DISALLOW_CONFIG_DATE_TIME - ), global); - assertEquals(0, local.size()); - } - - public void testSortToLocalAndGlobalWithCameraDisabled() { - final Bundle local = new Bundle(); - final Bundle global = new Bundle(); - - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA), - UserManagerInternal.OWNER_TYPE_DEVICE_OWNER, global, local); - assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), global); - assertEquals(0, local.size()); - global.clear(); - - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA), - UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, global, - local); - assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), global); - assertEquals(0, local.size()); - global.clear(); - - UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA), - UserManagerInternal.OWNER_TYPE_PROFILE_OWNER, global, local); - assertEquals(0, global.size()); - assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), local); - } - public void testMoveRestriction() { SparseArray<RestrictionsSet> localRestrictions = new SparseArray<>(); RestrictionsSet globalRestrictions = new RestrictionsSet(); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index d69e1b8786b4..8398585ca74a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -85,6 +85,9 @@ public class DexManagerTests { private TestData mBarUser0UnsupportedClassLoader; private TestData mBarUser0DelegateLastClassLoader; + private TestData mSystemServerJar; + private TestData mSystemServerJarInvalid; + private int mUser0; private int mUser1; @@ -108,6 +111,9 @@ public class DexManagerTests { mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); + mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); @@ -587,6 +593,25 @@ public class DexManagerTests { assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } + + @Test + public void testNotifySystemServerUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/system/framework/foo"); + notifyDexLoad(mSystemServerJar, dexFiles, mUser0); + PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); + assertIsUsedByOtherApps(mSystemServerJar, pui, false); + } + + @Test + public void testNotifySystemServerInvalidUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/data/foo"); + notifyDexLoad(mSystemServerJarInvalid, dexFiles, mUser0); + assertNoUseInfo(mSystemServerJarInvalid); + assertNoDclInfo(mSystemServerJarInvalid); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 5df4509af885..adf4551e79a8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -77,25 +77,25 @@ public class PackageDexUsageTests { String fooDataDir = "/data/user/0/com.google.foo/"; mFooBaseUser0 = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, ISA, false, true, fooPackageName); + fooCodeDir + "base.apk", 0, ISA, true, fooPackageName); mFooSplit1User0 = new TestData(fooPackageName, - fooCodeDir + "split-1.apk", 0, ISA, false, true, fooPackageName); + fooCodeDir + "split-1.apk", 0, ISA, true, fooPackageName); mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, - fooCodeDir + "split-2.apk", 0, ISA, true, true, "used.by.other.com"); + fooCodeDir + "split-2.apk", 0, ISA, true, "used.by.other.com"); mFooSecondary1User0 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 0, ISA, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 0, ISA, false, fooPackageName); mFooSecondary1User1 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 1, ISA, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 1, ISA, false, fooPackageName); mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, - fooDataDir + "sec-2.dex", 0, ISA, true, false, "used.by.other.com"); + fooDataDir + "sec-2.dex", 0, ISA, false, "used.by.other.com"); mInvalidIsa = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); + fooCodeDir + "base.apk", 0, "INVALID_ISA", true, "INALID_USER"); String barPackageName = "com.google.bar"; String barCodeDir = "/data/app/com.google.bar/"; @@ -103,11 +103,11 @@ public class PackageDexUsageTests { String barDataDir1 = "/data/user/1/com.google.bar/"; mBarBaseUser0 = new TestData(barPackageName, - barCodeDir + "base.apk", 0, ISA, false, true, barPackageName); + barCodeDir + "base.apk", 0, ISA, true, barPackageName); mBarSecondary1User0 = new TestData(barPackageName, - barDataDir + "sec-1.dex", 0, ISA, false, false, barPackageName); + barDataDir + "sec-1.dex", 0, ISA, false, barPackageName); mBarSecondary2User1 = new TestData(barPackageName, - barDataDir1 + "sec-2.dex", 1, ISA, false, false, barPackageName); + barDataDir1 + "sec-2.dex", 1, ISA, false, barPackageName); } @Test @@ -134,7 +134,9 @@ public class PackageDexUsageTests { public void testRecordSplitPrimarySequence() { // Assert new information. assertTrue(record(mFooBaseUser0)); - // Assert no new information. + assertTrue(record(mFooSplit1User0)); + // Assert no new information if we add again + assertFalse(record(mFooBaseUser0)); assertFalse(record(mFooSplit1User0)); assertPackageDexUsage(mFooBaseUser0); @@ -192,7 +194,7 @@ public class PackageDexUsageTests { for (int i = 1; i <= tooManyFiles; i++) { String fooPackageName = "com.google.foo"; TestData testData = new TestData(fooPackageName, - "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, false, + "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, fooPackageName); if (i < tooManyFiles) { assertTrue("Adding " + testData.mDexFile, record(testData)); @@ -200,7 +202,11 @@ public class PackageDexUsageTests { } else { assertFalse("Adding " + testData.mDexFile, record(testData)); } - assertPackageDexUsage(mPackageDexUsage, null, null, expectedSecondaries); + assertPackageDexUsage( + mPackageDexUsage, + /* usdeBy=*/ (Set<String>) null, + /* primaryDex= */ null, + expectedSecondaries); } } @@ -276,7 +282,7 @@ public class PackageDexUsageTests { Map<String, Set<String>> packageToCodePaths = new HashMap<>(); packageToCodePaths.put(mBarBaseUser0.mPackageName, new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); - mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<String>()); // Assert that only user 1 files are there. assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1); @@ -284,6 +290,41 @@ public class PackageDexUsageTests { } @Test + public void testSyncDataKeepPackages() { + PackageDexUsage packageDexUsage = new PackageDexUsage(); + // Write the record we want to keep and which won't be keep by default. + Set<String> fooUsers = new HashSet<>(Arrays.asList( + new String[] {mFooBaseUser0.mPackageName})); + assertTrue(record(packageDexUsage, mFooBaseUser0, fooUsers)); + // Write a record that would be kept by default. + Set<String> barUsers = new HashSet<>(Arrays.asList( + new String[] {"another.package", mFooBaseUser0.mPackageName})); + assertTrue(record(packageDexUsage, mBarBaseUser0, barUsers)); + + // Construct the user packages and their code paths (things that will be + // kept by default during sync). + Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + packageToUsersMap.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId))); + Map<String, Set<String>> packageToCodePaths = new HashMap<>(); + packageToCodePaths.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + + // Sync data. + List<String> keepData = new ArrayList<String>(); + keepData.add(mFooBaseUser0.mPackageName); + packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, keepData); + + // Assert that both packages are kept + assertPackageDexUsage(packageDexUsage, fooUsers, mFooBaseUser0); + // "another.package" should not be in the loading packages after sync. + Set<String> expectedBarUsers = new HashSet<>(Arrays.asList( + new String[] {mFooBaseUser0.mPackageName})); + assertPackageDexUsage(packageDexUsage, expectedBarUsers, + mBarBaseUser0.updateUsedBy(mFooBaseUser0.mPackageName)); + } + + @Test public void testRemovePackage() { // Record Bar secondaries for two different users. assertTrue(record(mBarSecondary1User0)); @@ -345,9 +386,8 @@ public class PackageDexUsageTests { mFooSplit2UsedByOtherApps0.mDexFile, mFooSplit2UsedByOtherApps0.mOwnerUserId, mFooSplit2UsedByOtherApps0.mLoaderIsa, - /*mIsUsedByOtherApps*/false, mFooSplit2UsedByOtherApps0.mPrimaryOrSplit, - mFooSplit2UsedByOtherApps0.mUsedBy); + /*usedBy=*/ null); assertPackageDexUsage(noLongerUsedByOtherApps); } @@ -371,19 +411,19 @@ public class PackageDexUsageTests { assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, usersExtra)); packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); // Verify that the users were recorded. Set<String> userAll = new HashSet<>(users); userAll.addAll(usersExtra); assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0, - mFooSecondary1User0); + mFooSecondary2UsedByOtherApps0); } @Test - public void testRecordDexFileUsersNotTheOwningPackage() { + public void testRecordDexFileUsersAndTheOwningPackage() { PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); Set<String> users = new HashSet<>(Arrays.asList( new String[] {mFooSplit2UsedByOtherApps0.mPackageName})); @@ -393,13 +433,13 @@ public class PackageDexUsageTests { assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); - packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); - // Verify that only the non owning packages were recorded. - assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooSplit2UsedByOtherApps0, - mFooSecondary1User0); + + Set<String> expectedUsers = new HashSet<>(users); + expectedUsers.addAll(usersExtra); + // Verify that all loading packages were recorded. + assertPackageDexUsage( + packageDexUsageRecordUsers, expectedUsers, mFooSplit2UsedByOtherApps0); } @Test @@ -421,44 +461,97 @@ public class PackageDexUsageTests { } @Test - public void testRecordClassLoaderContextTransitionFromUnknown() { - // Record a secondary dex file. - TestData unknownContext = mFooSecondary1User0.updateClassLoaderContext( - PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); - assertTrue(record(unknownContext)); - - assertPackageDexUsage(null, unknownContext); - writeAndReadBack(); - assertPackageDexUsage(null, unknownContext); - - // Now update the secondary dex record with a class loader context. This simulates the - // version 2 to version 3 upgrade. - - assertTrue(record(mFooSecondary1User0)); - - assertPackageDexUsage(null, mFooSecondary1User0); - writeAndReadBack(); - assertPackageDexUsage(null, mFooSecondary1User0); - } - - @Test public void testDexUsageClassLoaderContext() { final boolean isUsedByOtherApps = false; final int userId = 0; PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId, "valid_context", "arm"); - assertFalse(validContext.isUnknownClassLoaderContext()); + assertFalse(validContext.isUnsupportedClassLoaderContext()); assertFalse(validContext.isVariableClassLoaderContext()); PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId, PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm"); - assertFalse(variableContext.isUnknownClassLoaderContext()); + assertFalse(variableContext.isUnsupportedClassLoaderContext()); assertTrue(variableContext.isVariableClassLoaderContext()); + } - PackageDexUsage.DexUseInfo unknownContext = new DexUseInfo(isUsedByOtherApps, userId, - PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT, "arm"); - assertTrue(unknownContext.isUnknownClassLoaderContext()); - assertFalse(unknownContext.isVariableClassLoaderContext()); + @Test + public void testRead() { + String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + // Equivalent to + // record(mFooSplit2UsedByOtherApps0); + // record(mFooSecondary1User0); + // record(mFooSecondary2UsedByOtherApps0); + // record(mBarBaseUser0); + // record(mBarSecondary1User0); + String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n" + + "com.google.foo\n" + + "+/data/app/com.google.foo/split-2.apk\n" + + "@used.by.other.com\n" + + "#/data/user/0/com.google.foo/sec-2.dex\n" + + "0,1," + ISA + "\n" + + "@used.by.other.com\n" + + "PCL[/data/user/0/com.google.foo/sec-2.dex]\n" + + "#/data/user/0/com.google.foo/sec-1.dex\n" + + "0,0," + ISA + "\n" + + "@\n" + + "PCL[/data/user/0/com.google.foo/sec-1.dex]\n" + + "com.google.bar\n" + + "+/data/app/com.google.bar/base.apk\n" + + "@com.google.bar\n" + + "#/data/user/0/com.google.bar/sec-1.dex\n" + + "0,0," + ISA + "\n" + + "@\n" + + "PCL[/data/user/0/com.google.bar/sec-1.dex]"; + + PackageDexUsage packageDexUsage = new PackageDexUsage(); + try { + packageDexUsage.read(new StringReader(content)); + } catch (IOException e) { + fail(); + } + + // After the read we must sync the data to fill the missing information on the code paths. + Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + Map<String, Set<String>> packageToCodePaths = new HashMap<>(); + + // Handle foo package. + packageToUsersMap.put( + mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); + packageToCodePaths.put( + mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mDexFile, + mFooSplit1User0.mDexFile, mFooBaseUser0.mDexFile))); + // Handle bar package. + packageToUsersMap.put( + mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId))); + packageToCodePaths.put( + mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + // Handle the loading package. + packageToUsersMap.put( + mFooSplit2UsedByOtherApps0.mUsedBy, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); + + // Sync the data. + packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<>()); + + // Assert foo code paths. + assertPackageDexUsage( + packageDexUsage, + /*nonDefaultUsers=*/ null, + mFooSplit2UsedByOtherApps0, + mFooSecondary2UsedByOtherApps0, + mFooSecondary1User0); + + // Assert bar code paths. + assertPackageDexUsage( + packageDexUsage, + /*nonDefaultUsers=*/ null, + mBarBaseUser0, + mBarSecondary1User0); } @Test @@ -483,77 +576,19 @@ public class PackageDexUsageTests { } @Test - public void testReadVersion1() { + public void testEnsureLoadingPackagesCanBeExtended() { String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); - // Equivalent to - // record(mFooSplit2UsedByOtherApps0); - // record(mFooSecondary1User0); - // record(mFooSecondary2UsedByOtherApps0); - // record(mBarBaseUser0); - // record(mBarSecondary1User0); - String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__1\n" - + "com.google.foo,1\n" - + "#/data/user/0/com.google.foo/sec-1.dex\n" - + "0,0," + isa + "\n" - + "#/data/user/0/com.google.foo/sec-2.dex\n" - + "0,1," + isa + "\n" - + "com.google.bar,0\n" - + "#/data/user/0/com.google.bar/sec-1.dex\n" - + "0,0," + isa + "\n"; - + String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n" + + "com.google.foo\n" + + "+/data/app/com.google.foo/split-2.apk\n" + + "@\n"; PackageDexUsage packageDexUsage = new PackageDexUsage(); try { packageDexUsage.read(new StringReader(content)); } catch (IOException e) { fail(); } - - // After the read we must sync the data to fill the missing information on the code paths. - Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); - Map<String, Set<String>> packageToCodePaths = new HashMap<>(); - - // Handle foo package. - packageToUsersMap.put(mFooSplit2UsedByOtherApps0.mPackageName, - new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); - packageToCodePaths.put(mFooSplit2UsedByOtherApps0.mPackageName, - new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mDexFile, - mFooSplit1User0.mDexFile, mFooBaseUser0.mDexFile))); - // Handle bar package. - packageToUsersMap.put(mBarBaseUser0.mPackageName, - new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId))); - packageToCodePaths.put(mBarBaseUser0.mPackageName, - new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); - - // Sync the data. - packageDexUsage.syncData(packageToUsersMap, packageToCodePaths); - - // Update the class loaders to unknown before asserting if needed. Before version 2 we - // didn't have any. - String unknown = PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT; - TestData fooBaseUser0 = mFooBaseUser0.updateClassLoaderContext(unknown); - TestData fooSplit1User0 = mFooSplit1User0.updateClassLoaderContext(unknown); - TestData fooSplit2UsedByOtherApps0 = - mFooSplit2UsedByOtherApps0.updateClassLoaderContext(unknown); - TestData fooSecondary1User0 = mFooSecondary1User0.updateClassLoaderContext(unknown); - TestData fooSecondary2UsedByOtherApps0 = - mFooSecondary2UsedByOtherApps0.updateClassLoaderContext(unknown); - TestData barBaseUser0 = mBarBaseUser0.updateClassLoaderContext(unknown); - TestData barSecondary1User0 = mBarSecondary1User0.updateClassLoaderContext(unknown); - - // Assert foo code paths. Note that we ignore the users during upgrade. - final Set<String> ignoredUsers = null; - assertPackageDexUsage(packageDexUsage, ignoredUsers, - fooSplit2UsedByOtherApps0, fooSecondary1User0, fooSecondary2UsedByOtherApps0); - // Because fooSplit2UsedByOtherApps0 is used by others, all the other code paths must - // share the same data. - assertPackageDexUsage(packageDexUsage, ignoredUsers, - fooSplit1User0.updateUseByOthers(true), - fooSecondary1User0, fooSecondary2UsedByOtherApps0); - assertPackageDexUsage(packageDexUsage, ignoredUsers, fooBaseUser0.updateUseByOthers(true), - fooSecondary1User0, fooSecondary2UsedByOtherApps0); - - // Assert bar code paths. Note that we ignore the users during upgrade. - assertPackageDexUsage(packageDexUsage, ignoredUsers, barBaseUser0, barSecondary1User0); + record(packageDexUsage, mFooSplit2UsedByOtherApps0, mFooSplit2UsedByOtherApps0.getUsedBy()); } private void assertPackageDexUsage(TestData primary, TestData... secondaries) { @@ -570,16 +605,18 @@ public class PackageDexUsageTests { String packageName = primary == null ? secondaries.get(0).mPackageName : primary.mPackageName; - boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; + boolean primaryUsedByOtherApps = primary != null && primary.isUsedByOtherApps(); PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); // Check package use info assertNotNull(pInfo); if (primary != null) { - assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); if (users != null) { assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users); + } else if (pInfo.getLoadingPackages(primary.mDexFile) != null) { + assertEquals(pInfo.getLoadingPackages(primary.mDexFile), primary.getUsedBy()); } + assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); } Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap(); @@ -589,13 +626,15 @@ public class PackageDexUsageTests { for (TestData testData : secondaries) { DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile); assertNotNull(dInfo); - assertEquals(testData.mUsedByOtherApps, dInfo.isUsedByOtherApps()); + if (users != null) { + assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), users); + } else { + assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), testData.getUsedBy()); + } + assertEquals(testData.isUsedByOtherApps(), dInfo.isUsedByOtherApps()); assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); assertEquals(1, dInfo.getLoaderIsas().size()); assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); - if (users != null) { - assertEquals(dInfo.getLoadingPackages(), users); - } assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext()); } @@ -603,7 +642,7 @@ public class PackageDexUsageTests { private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); } @@ -611,7 +650,7 @@ public class PackageDexUsageTests { boolean result = true; for (String user : users) { result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); } return result; @@ -640,37 +679,49 @@ public class PackageDexUsageTests { private final String mDexFile; private final int mOwnerUserId; private final String mLoaderIsa; - private final boolean mUsedByOtherApps; private final boolean mPrimaryOrSplit; private final String mUsedBy; private final String mClassLoaderContext; private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { - this(packageName, dexFile, ownerUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, - usedBy, "DefaultClassLoaderContextFor_" + dexFile); + String loaderIsa, boolean primaryOrSplit, String usedBy) { + this(packageName, dexFile, ownerUserId, loaderIsa, primaryOrSplit, + usedBy, "PCL[" + dexFile + "]"); } private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy, + String loaderIsa, boolean primaryOrSplit, String usedBy, String classLoaderContext) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; mLoaderIsa = loaderIsa; - mUsedByOtherApps = isUsedByOtherApps; mPrimaryOrSplit = primaryOrSplit; mUsedBy = usedBy; mClassLoaderContext = classLoaderContext; } private TestData updateClassLoaderContext(String newContext) { - return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mPrimaryOrSplit, mUsedBy, newContext); } - private TestData updateUseByOthers(boolean newUsedByOthers) { - return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, newUsedByOthers, - mPrimaryOrSplit, mUsedBy, mClassLoaderContext); + private TestData updateUsedBy(String newUsedBy) { + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, + mPrimaryOrSplit, newUsedBy, mClassLoaderContext); + } + + private boolean isUsedByOtherApps() { + return mUsedBy != null && !mPackageName.equals(mUsedBy); + } + + private Set<String> getUsedBy() { + Set<String> users = new HashSet<>(); + if ((mUsedBy != null) && (mPrimaryOrSplit || isUsedByOtherApps())) { + // We do not store the loading package for secondary dex files + // which are not used by others. + users.add(mUsedBy); + } + return users; } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java new file mode 100644 index 000000000000..404f29c118d0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.parsing.library; + +import android.os.Build; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.PackageImpl; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link ComGoogleAndroidMapsUpdater} + */ +@Presubmit +@SmallTest +@RunWith(JUnit4.class) +public class ComGoogleAndroidMapsUpdaterTest extends PackageSharedLibraryUpdaterTest { + + @Test + public void otherUsesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .addUsesLibrary("com.google.android.maps") + .hideAsParsed()); + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .hideAsParsed()) + .hideAsFinal(); + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("com.google.android.maps") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesOptionalLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesOptionalLibrary("com.google.android.maps") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { + checkBackwardsCompatibility(before, after, ComGoogleAndroidMapsUpdater::new); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index ca3886092cf1..09c8142105cc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -148,6 +148,23 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses a + * {@link ComGoogleAndroidMapsUpdater}. + */ + @Test + public void com_google_android_maps_in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("com.google.android.maps") + .hideAsParsed()); + + ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT); + + checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); + } + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance); } diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java new file mode 100644 index 000000000000..cfeadc6893db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static android.os.UserHandle.USER_SYSTEM; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.content.Context; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:SettingsStatsUtilTest + */ +@RunWith(AndroidJUnit4.class) +public class SettingsStatsUtilTest { + private static final String[] KEYS = new String[]{ + "screen_auto_brightness_adj", + "font_scale" + }; + private static final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ"; + private static final String FLAG = "testflag"; + private Context mContext; + + @Before + public void setUp() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + FLAG, + "", + false /* makeDefault*/); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + public void getList_emptyString_nullValue() { + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_notValidString_nullValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, "abcd", false); + + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_validString_correctValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, ENCODED, false); + + assertArrayEquals(KEYS, SettingsStatsUtil.getList(FLAG).element); + } + + @Test + public void logGlobalSettings_noWhitelist_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", "", false); + + assertEquals(0, SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } + + @Test + public void logGlobalSettings_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", ENCODED, false); + + assertEquals(KEYS.length * 4, + SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index eef9012b005b..10981ab49513 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -16,6 +16,8 @@ package com.android.server.systemconfig; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; @@ -164,7 +166,7 @@ public class SystemConfigTest { mSysConfig.readPermissions(folder, /* No permission needed anyway */ 0); - final ArrayMap<String, Boolean> packageOneExpected = new ArrayMap<>(); + final ArrayMap<String, Boolean> packageOneExpected = new ArrayMap<>(); packageOneExpected.put("com.android.package1.Full", true); packageOneExpected.put("com.android.package1.Relative", false); @@ -181,7 +183,47 @@ public class SystemConfigTest { } /** + * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_APP_CONFIGS} + * permission flag for the tag: whitelisted-staged-installer. + */ + @Test + public void readPermissions_allowAppConfigs_parsesStagedInstallerWhitelist() + throws IOException { + final String contents = + "<config>\n" + + " <whitelisted-staged-installer package=\"com.android.package1\" />\n" + + "</config>"; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); + + assertThat(mSysConfig.getWhitelistedStagedInstallers()) + .containsExactly("com.android.package1"); + } + + /** + * Tests that readPermissions works correctly without {@link SystemConfig#ALLOW_APP_CONFIGS} + * permission flag for the tag: whitelisted-staged-installer. + */ + @Test + public void readPermissions_notAllowAppConfigs_wontParseStagedInstallerWhitelist() + throws IOException { + final String contents = + "<config>\n" + + " <whitelisted-staged-installer package=\"com.android.package1\" />\n" + + "</config>"; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all but ALLOW_APP_CONFIGS flag */ ~0x08); + + assertThat(mSysConfig.getWhitelistedStagedInstallers()).isEmpty(); + } + + /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. + * * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed * @return the folder */ @@ -194,7 +236,8 @@ public class SystemConfigTest { /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. - * @param folder pre-existing subdirectory of mTemporaryFolder to put the file + * + * @param folder pre-existing subdirectory of mTemporaryFolder to put the file * @param fileName name of the file (e.g. filename.xml) to create * @param contents contents to write to the file * @return the folder containing the newly created file (not the file itself!) diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java new file mode 100644 index 000000000000..72580a3b98c2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.textclassifier; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Sanity test for {@link IconsContentProvider}. + */ +@RunWith(AndroidJUnit4.class) +public final class IconsContentProviderTest { + + @Test + public void testLoadResource() { + final Context context = ApplicationProvider.getApplicationContext(); + // Testing with the android package name because this is the only package name + // that returns the same uri across multiple classloaders. + final String packageName = "android"; + final int resId = android.R.drawable.btn_star; + final Uri uri = IconsUriHelper.getInstance().getContentUri(packageName, resId); + + final Drawable expected = Icon.createWithResource(packageName, resId).loadDrawable(context); + // Ensure we are testing with a non-empty image. + assertThat(expected.getIntrinsicWidth()).isGreaterThan(0); + assertThat(expected.getIntrinsicHeight()).isGreaterThan(0); + + final Drawable actual = Icon.createWithContentUri(uri).loadDrawable(context); + assertThat(actual).isNotNull(); + assertThat(IconsContentProvider.getBitmapData(actual)) + .isEqualTo(IconsContentProvider.getBitmapData(expected)); + } + + @Test + public void testLoadResource_badUri() { + final Uri badUri = new Uri.Builder() + .scheme("content") + .authority(IconsUriHelper.AUTHORITY) + .path("badPackageId") + .appendPath("1234") + .build(); + + final Context context = ApplicationProvider.getApplicationContext(); + assertThat(Icon.createWithContentUri(badUri).loadDrawable(context)).isNull(); + } +} + diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java new file mode 100644 index 000000000000..96f09d965b13 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.textclassifier; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.textclassifier.IconsUriHelper.ResourceInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link IconsUriHelper}. + */ +@RunWith(AndroidJUnit4.class) +public final class IconsUriHelperTest { + + private IconsUriHelper mIconsUriHelper; + + @Before + public void setUp() { + mIconsUriHelper = IconsUriHelper.newInstanceForTesting(null); + } + + @Test + public void testGetContentUri() { + final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId"); + final Uri expected = new Uri.Builder() + .scheme("content") + .authority(IconsUriHelper.AUTHORITY) + .path("pkgId") + .appendPath("1234") + .build(); + + final Uri actual = iconsUriHelper.getContentUri("com.package.name", 1234); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testGetContentUri_multiplePackages() { + final Uri uri1 = mIconsUriHelper.getContentUri("com.package.name1", 1234); + final Uri uri2 = mIconsUriHelper.getContentUri("com.package.name2", 5678); + + assertThat(uri1.getScheme()).isEqualTo("content"); + assertThat(uri2.getScheme()).isEqualTo("content"); + + assertThat(uri1.getAuthority()).isEqualTo(IconsUriHelper.AUTHORITY); + assertThat(uri2.getAuthority()).isEqualTo(IconsUriHelper.AUTHORITY); + + assertThat(uri1.getPathSegments().get(1)).isEqualTo("1234"); + assertThat(uri2.getPathSegments().get(1)).isEqualTo("5678"); + } + + @Test + public void testGetContentUri_samePackageIdForSamePackageName() { + final String packageName = "com.package.name"; + final Uri uri1 = mIconsUriHelper.getContentUri(packageName, 1234); + final Uri uri2 = mIconsUriHelper.getContentUri(packageName, 5678); + + final String id1 = uri1.getPathSegments().get(0); + final String id2 = uri2.getPathSegments().get(0); + + assertThat(id1).isEqualTo(id2); + } + + @Test + public void testGetResourceInfo() { + mIconsUriHelper.getContentUri("com.package.name1", 123); + final Uri uri = mIconsUriHelper.getContentUri("com.package.name2", 456); + mIconsUriHelper.getContentUri("com.package.name3", 789); + + final ResourceInfo res = mIconsUriHelper.getResourceInfo(uri); + assertThat(res.packageName).isEqualTo("com.package.name2"); + assertThat(res.id).isEqualTo(456); + } + + @Test + public void testGetResourceInfo_unrecognizedUri() { + final Uri uri = new Uri.Builder() + .scheme("content") + .authority(IconsUriHelper.AUTHORITY) + .path("unrecognized") + .appendPath("1234") + .build(); + assertThat(mIconsUriHelper.getResourceInfo(uri)).isNull(); + } + + @Test + public void testGetResourceInfo_invalidScheme() { + final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId"); + iconsUriHelper.getContentUri("com.package.name", 1234); + + final Uri uri = new Uri.Builder() + .scheme("file") + .authority(IconsUriHelper.AUTHORITY) + .path("pkgId") + .appendPath("1234") + .build(); + assertThat(iconsUriHelper.getResourceInfo(uri)).isNull(); + } + + @Test + public void testGetResourceInfo_invalidAuthority() { + final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId"); + iconsUriHelper.getContentUri("com.package.name", 1234); + + final Uri uri = new Uri.Builder() + .scheme("content") + .authority("invalid.authority") + .path("pkgId") + .appendPath("1234") + .build(); + assertThat(iconsUriHelper.getResourceInfo(uri)).isNull(); + } +} + diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 327cfc74dac5..39062f017a73 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -818,6 +818,69 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RESTRICTED); } + /** + * Test that an app is "timed out" into the RESTRICTED bucket if prediction tries to put it into + * a low bucket after the RESTRICTED timeout. + */ + @Test + public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() { + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Predict to RARE Not long enough to time out into RESTRICTED. + mInjector.mElapsedRealtime += RARE_THRESHOLD; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_PREDICTED); + assertBucket(STANDBY_BUCKET_RARE); + + // Add a short timeout event + mInjector.mElapsedRealtime += 1000; + reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE); + mInjector.mElapsedRealtime += 1000; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Long enough that it could have timed out into RESTRICTED. Instead of reverting to + // predicted RARE, should go into RESTRICTED + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Ensure that prediction can still raise it out despite this override. + mInjector.mElapsedRealtime += 1; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, + REASON_MAIN_PREDICTED); + assertBucket(STANDBY_BUCKET_ACTIVE); + } + + /** + * Test that an app is "timed out" into the RESTRICTED bucket if prediction tries to put it into + * a low bucket after the RESTRICTED timeout. + */ + @Test + public void testRestrictedTimeoutOverridesPredictionLowBucket() { + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + + // Not long enough to time out into RESTRICTED. + mInjector.mElapsedRealtime += RARE_THRESHOLD; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_PREDICTED); + assertBucket(STANDBY_BUCKET_RARE); + + mInjector.mElapsedRealtime += 1; + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + + // Long enough that it could have timed out into RESTRICTED. + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, + REASON_MAIN_PREDICTED); + assertBucket(STANDBY_BUCKET_ACTIVE); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_PREDICTED); + assertBucket(STANDBY_BUCKET_RESTRICTED); + } + @Test public void testPredictionRaiseFromRestrictedTimeout_highBucket() { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 2077ecb2799e..96c69af036b6 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -197,7 +197,8 @@ public class ShortcutManagerTestUtils { final String PREFIX = "Launcher: ComponentInfo{"; final String POSTFIX = "}"; final List<String> result = runShortcutCommandForSuccess( - instrumentation, "get-default-launcher"); + instrumentation, "get-default-launcher --user " + + instrumentation.getContext().getUserId()); for (String s : result) { if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) { return s.substring(PREFIX.length(), s.length() - POSTFIX.length()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index 47ad83147262..a23ade68b344 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -18,7 +18,6 @@ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; @@ -77,8 +76,8 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { @Test public void testInvalidShortcutFlagEnabled_looksUpCorrectChannel() { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); NotificationChannelExtractor extractor = new NotificationChannelExtractor(); extractor.setConfig(mConfig); @@ -97,7 +96,8 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); when(mConfig.getConversationNotificationChannel( - any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId(mContext)), eq(true), eq(false))) + any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId(mContext)), + eq(true), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); @@ -106,8 +106,8 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { @Test public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); NotificationChannelExtractor extractor = new NotificationChannelExtractor(); extractor.setConfig(mConfig); 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 f9596b53407f..15220e1ff54a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4791,7 +4791,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), "Text", 2000, 0, null); - verify(mStatusBar).showToast(any(), any(), any(), any(), anyInt(), any()); + verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 00b9273c1eb1..3139bfaaf1f5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -63,7 +63,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; -import android.util.FeatureFlagUtils; import android.widget.RemoteViews; import androidx.test.filters.SmallTest; @@ -124,8 +123,8 @@ public class NotificationRecordTest extends UiServiceTestCase { when(mMockContext.getResources()).thenReturn(getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); - Settings.Global.putString(mContentResolver, - FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); ApplicationInfo appInfo = new ApplicationInfo(); appInfo.targetSdkVersion = Build.VERSION_CODES.O; when(mMockContext.getApplicationInfo()).thenReturn(appInfo); @@ -1138,8 +1137,8 @@ public class NotificationRecordTest extends UiServiceTestCase { @Test public void testIsConversation_bypassShortcutFlagEnabled() { - Settings.Global.putString(mContentResolver, - FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); StatusBarNotification sbn = getMessagingStyleNotification(); NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); record.setShortcutInfo(null); 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 af605112307f..ed5ec6ac785b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -22,7 +22,6 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; @@ -2922,9 +2921,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testPlaceholderConversationId_flagOn() throws Exception { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); + public void testPlaceholderConversationId_shortcutNotRequired() throws Exception { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "<ranking version=\"1\">\n" @@ -2942,9 +2942,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testPlaceholderConversationId_flagOff() throws Exception { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); + public void testPlaceholderConversationId_shortcutRequired() throws Exception { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "<ranking version=\"1\">\n" @@ -2962,9 +2962,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testNormalConversationId_flagOff() throws Exception { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); + public void testNormalConversationId_shortcutRequired() throws Exception { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "<ranking version=\"1\">\n" @@ -2982,9 +2982,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testNoConversationId_flagOff() throws Exception { - Settings.Global.putString( - mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); + public void testNoConversationId_shortcutRequired() throws Exception { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "<ranking version=\"1\">\n" diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 12bdec6ec1e1..0568be8d7fa6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; +import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; import static com.android.server.wm.DisplayArea.Type.ANY; @@ -72,7 +73,11 @@ public class DisplayAreaPolicyBuilderTest { WindowManagerService wms = mSystemServices.getWindowManagerService(); DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms); DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime"); - DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks"); + DisplayContent displayContent = mock(DisplayContent.class); + TaskDisplayArea taskDisplayArea = new TaskDisplayArea(displayContent, wms, "Tasks", + FEATURE_DEFAULT_TASK_CONTAINER); + List<TaskDisplayArea> taskDisplayAreaList = new ArrayList<>(); + taskDisplayAreaList.add(taskDisplayArea); final Feature foo; final Feature bar; @@ -86,7 +91,7 @@ public class DisplayAreaPolicyBuilderTest { .all() .except(TYPE_STATUS_BAR) .build()) - .build(wms, mock(DisplayContent.class), root, ime, tasks); + .build(wms, displayContent, root, ime, taskDisplayAreaList); policy.attachDisplayAreas(); @@ -98,9 +103,9 @@ public class DisplayAreaPolicyBuilderTest { assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)), is(not(decendantOfOneOf(policy.getDisplayAreas(bar))))); - assertThat(tasks, + assertThat(taskDisplayArea, is(decendantOfOneOf(policy.getDisplayAreas(foo)))); - assertThat(tasks, + assertThat(taskDisplayArea, is(decendantOfOneOf(policy.getDisplayAreas(bar)))); assertThat(ime, @@ -109,7 +114,8 @@ public class DisplayAreaPolicyBuilderTest { is(decendantOfOneOf(policy.getDisplayAreas(bar)))); List<DisplayArea<?>> actualOrder = collectLeafAreas(root); - Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks); + Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, + taskDisplayArea); actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList()); Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java index 4e4627bf7e52..6834ee5f3950 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -77,8 +77,7 @@ public class DisplayAreaProviderTest { @Override public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, - DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, - TaskDisplayArea taskDisplayArea) { + DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer) { throw new RuntimeException("test stub"); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 618e6086b582..880c486c15af 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -54,17 +54,6 @@ public class DisplayAreaTest { } @Test - public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() { - WindowManagerService wms = mWmsRule.getWindowManagerService(); - DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent"); - DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1"); - DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2"); - - parent.addChild(child1, 0); - assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0)); - } - - @Test public void testType_typeOf() { WindowManagerService wms = mWmsRule.getWindowManagerService(); @@ -87,10 +76,10 @@ public class DisplayAreaTest { checkSiblings(BELOW_TASKS, ABOVE_TASKS); checkSiblings(ANY, ABOVE_TASKS); checkSiblings(ABOVE_TASKS, ABOVE_TASKS); + checkSiblings(ANY, ANY); assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS)); assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY)); - assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY)); assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 89bc65b5a44d..b6eb9010ce90 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -30,7 +30,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -41,7 +40,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.platform.test.annotations.Presubmit; import android.util.IntArray; @@ -49,7 +47,6 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.AfterClass; @@ -168,6 +165,18 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test + public void testControlsForDispatch_forceShowSystemBarsFromExternal_appHasNoControl() { + mDisplayContent.getDisplayPolicy().setForceShowSystemBars(true); + addWindow(TYPE_STATUS_BAR, "statusBar"); + addWindow(TYPE_NAVIGATION_BAR, "navBar"); + + final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); + + // The focused app window cannot control system bars. + assertNull(controls); + } + + @Test public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() { addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar") .getControllableInsetProvider().getSource().setVisible(false); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index add4e9cf3948..ea933dfe42dc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1124,8 +1124,6 @@ public class RecentTasksTest extends ActivityTestsBase { } }); assertSecurityException(expectCallable, - () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, () -> mService.startActivityFromRecents(0, new Bundle())); assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true)); assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 6d2b7b1e86fe..f19550ced0bf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -23,7 +23,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -318,7 +317,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Assume activity transition should animate when no // IRecentsAnimationController#setDeferCancelUntilNextTransition called. assertFalse(mController.shouldDeferCancelWithScreenshot()); - assertTrue(activity.shouldAnimate(TRANSIT_ACTIVITY_CLOSE)); + assertTrue(activity.shouldAnimate()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 881561f5750b..1f6ba7adf114 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -238,9 +238,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { assertTrue(targetActivity.mLaunchTaskBehind); anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome"); - // The current top activity is not the recents so the animation should be canceled. - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any() /* reason */); // The test uses mocked RecentsAnimationController so we have to invoke the callback // manually to simulate the flow. @@ -279,10 +276,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { fullscreenStack.moveToFront("Activity start"); - // Ensure that the recents animation was canceled by cancelAnimationSynchronously(). - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any()); - // Assume recents animation already started, set a state that cancel recents animation // with screenshot. doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 673469474709..e47792f4920c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -243,6 +243,26 @@ public class SizeCompatTests extends ActivityTestsBase { } @Test + public void testAspectRatioMatchParentBoundsAndImeAttachable() { + setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000) + .setSystemDecorations(true).build()); + prepareUnresizable(2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + assertFitted(); + + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity); + // Because the aspect ratio of display doesn't exceed the max aspect ratio of activity. + // The activity should still fill its parent container and IME can attach to the activity. + assertTrue(mActivity.matchParentBounds()); + assertTrue(mActivity.mDisplayContent.isImeAttachedToApp()); + + final Rect letterboxInnerBounds = new Rect(); + mActivity.getLetterboxInnerBounds(letterboxInnerBounds); + // The activity should not have letterbox. + assertTrue(letterboxInnerBounds.isEmpty()); + } + + @Test public void testMoveToDifferentOrientDisplay() { setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 19824bf73c1c..a7085334bece 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -16,8 +16,18 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -27,6 +37,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import android.platform.test.annotations.Presubmit; @@ -38,10 +51,10 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}. + * Tests for the {@link TaskDisplayArea} container. * * Build/Install/Run: - * atest WmTests:TaskStackContainersTests + * atest WmTests:TaskDisplayAreaTests */ @SmallTest @Presubmit @@ -134,4 +147,49 @@ public class TaskDisplayAreaTests extends WindowTestsBase { assertEquals("The testing DisplayContent should be moved to top with task", mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedStack); } + + @Test + public void testReuseTaskAsStack() { + final Task candidateTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task newStack = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea(); + doReturn(newStack).when(taskDisplayArea).createStack(anyInt(), anyInt(), anyBoolean(), + any(), any(), anyBoolean()); + + final int type = ACTIVITY_TYPE_STANDARD; + assertGetOrCreateStack(WINDOWING_MODE_FULLSCREEN, type, candidateTask, + true /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_UNDEFINED, type, candidateTask, + true /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, type, candidateTask, + true /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_FREEFORM, type, candidateTask, + true /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_MULTI_WINDOW, type, candidateTask, + true /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, type, candidateTask, + false /* reuseCandidate */); + assertGetOrCreateStack(WINDOWING_MODE_PINNED, type, candidateTask, + false /* reuseCandidate */); + + final int windowingMode = WINDOWING_MODE_FULLSCREEN; + assertGetOrCreateStack(windowingMode, ACTIVITY_TYPE_HOME, candidateTask, + false /* reuseCandidate */); + assertGetOrCreateStack(windowingMode, ACTIVITY_TYPE_RECENTS, candidateTask, + false /* reuseCandidate */); + assertGetOrCreateStack(windowingMode, ACTIVITY_TYPE_ASSISTANT, candidateTask, + false /* reuseCandidate */); + assertGetOrCreateStack(windowingMode, ACTIVITY_TYPE_DREAM, candidateTask, + false /* reuseCandidate */); + } + + private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask, + boolean reuseCandidate) { + final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea(); + final ActivityStack stack = taskDisplayArea.getOrCreateStack(windowingMode, activityType, + false /* onTop */, null /* intent */, candidateTask /* candidateTask */); + assertEquals(reuseCandidate, stack == candidateTask); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 62d311ae7b36..f275e378ed26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -201,6 +201,21 @@ public class TaskOrganizerTests extends WindowTestsBase { } @Test + public void testTaskNoDraw() throws RemoteException { + final ActivityStack stack = createStack(); + final Task task = createTask(stack, false /* fakeDraw */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, never()).onTaskAppeared(any()); + assertTrue(stack.isOrganized()); + + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); + verify(organizer, never()).onTaskVanished(any()); + assertFalse(stack.isOrganized()); + } + + @Test public void testClearOrganizer() throws RemoteException { final ActivityStack stack = createStack(); final Task task = createTask(stack); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index d48e82723295..413ae134fe18 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -33,6 +33,8 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import android.app.WindowConfiguration; @@ -144,7 +146,7 @@ public class TaskStackTests extends WindowTestsBase { // Stack removal is deferred if one of its child is animating. doReturn(true).when(stack).hasWindowsAlive(); - doReturn(true).when(task).isAnimating(TRANSITION | CHILDREN); + doReturn(true).when(task).isAnimating(eq(TRANSITION | CHILDREN), anyInt()); stack.removeIfPossible(); // For the case of deferred removal the task controller will still be connected to the its diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 18737c2b4bb7..d2a2732d60ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -43,7 +43,9 @@ class TestDisplayContent extends DisplayContent { // hard-code to FULLSCREEN for tests. setWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(this); - spyOn(mTaskContainers); + for (int i = getTaskDisplayAreaCount() - 1; i >= 0; --i) { + spyOn(getTaskDisplayAreaAt(i)); + } final DisplayRotation displayRotation = getDisplayRotation(); spyOn(displayRotation); @@ -137,6 +139,7 @@ class TestDisplayContent extends DisplayContent { spyOn(displayPolicy); if (mSystemDecorations) { doReturn(true).when(newDisplay).supportsSystemDecorations(); + doReturn(true).when(displayPolicy).hasNavigationBar(); } else { doReturn(false).when(displayPolicy).hasNavigationBar(); doReturn(false).when(displayPolicy).hasStatusBar(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 7a075a26cb31..4a8e8dafb57d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -120,8 +120,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { IWindow iWindow = mock(IWindow.class); doReturn(mock(IBinder.class)).when(iWindow).asBinder(); window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, activity, - "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, - mock(Session.class), iWindow, mPowerManagerWrapper); + "Starting window", 0 /* ownerId */, 0 /* userId*/, false /* internalWindows */, + wm, mock(Session.class), iWindow, mPowerManagerWrapper); activity.startingWindow = window; } if (mRunnableWhenAddingSplashScreen != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 118c2e4db208..353c781c15ef 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -35,6 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -423,6 +425,35 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testIsAnimating_typesToCheck() { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); + final TestWindowContainer window = builder.setIsAnimating(true).setLayer(0).build(); + + assertTrue(window.isAnimating()); + assertFalse(window.isAnimating(0, ANIMATION_TYPE_SCREEN_ROTATION)); + assertTrue(window.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION)); + assertFalse(window.isAnimatingExcluding(0, ANIMATION_TYPE_APP_TRANSITION)); + + final TestWindowContainer child = window.addChildWindow(); + assertFalse(child.isAnimating()); + assertTrue(child.isAnimating(PARENTS)); + assertTrue(child.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)); + assertFalse(child.isAnimating(PARENTS, ANIMATION_TYPE_SCREEN_ROTATION)); + + final WindowState windowState = createWindow(null /* parent */, TYPE_BASE_APPLICATION, + mDisplayContent, "TestWindowState"); + WindowContainer parent = windowState.getParent(); + spyOn(windowState.mSurfaceAnimator); + doReturn(true).when(windowState.mSurfaceAnimator).isAnimating(); + doReturn(ANIMATION_TYPE_APP_TRANSITION).when( + windowState.mSurfaceAnimator).getAnimationType(); + assertTrue(parent.isAnimating(CHILDREN)); + + windowState.setControllableInsetProvider(mock(InsetsSourceProvider.class)); + assertFalse(parent.isAnimating(CHILDREN)); + } + + @Test public void testIsVisible() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); @@ -895,6 +926,7 @@ public class WindowContainerTests extends WindowTestsBase { mWaitForTransitStart = waitTransitStart; spyOn(mSurfaceAnimator); doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating(); + doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType(); } TestWindowContainer getParentWindow() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index da4bde59a09e..79b9ae1b902a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -16,13 +16,18 @@ package com.android.server.wm; +import static android.os.Process.INVALID_UID; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.os.IBinder; @@ -85,4 +90,19 @@ public class WindowManagerServiceTests extends WindowTestsBase { assertFalse(windowToken.mRoundedCornerOverlay); assertTrue(windowToken.mFromClientToken); } + + @Test(expected = SecurityException.class) + public void testRemoveWindowToken_ownerUidNotMatch_throwException() { + IBinder token = mock(IBinder.class); + mWm.addWindowTokenWithOptions(token, TYPE_TOAST, mDisplayContent.getDisplayId(), + null /* options */, null /* options */); + + spyOn(mWm); + when(mWm.checkCallingPermission(anyString(), anyString())).thenReturn(false); + WindowToken windowToken = mWm.mRoot.getWindowToken(token); + spyOn(windowToken); + when(windowToken.getOwnerUid()).thenReturn(INVALID_UID); + + mWm.removeWindowToken(token, mDisplayContent.getDisplayId()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 820d3816a6f6..71b35b62366e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -40,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; 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.doThrow; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; @@ -47,6 +48,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; @@ -55,6 +58,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -65,6 +69,7 @@ import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.Size; import android.view.DisplayCutout; @@ -568,6 +573,36 @@ public class WindowStateTests extends WindowTestsBase { } @Test + public void testReportResizedWithRemoteException() { + final WindowState win = mChildAppWindowAbove; + makeWindowVisible(win, win.getParentWindow()); + win.mLayoutSeq = win.getDisplayContent().mLayoutSeq; + win.updateResizingWindowIfNeeded(); + + assertThat(mWm.mResizingWindows).contains(win); + assertTrue(win.getOrientationChanging()); + + mWm.mResizingWindows.remove(win); + spyOn(win.mClient); + try { + doThrow(new RemoteException("test")).when(win.mClient).resized(any() /* frame */, + any() /* contentInsets */, any() /* visibleInsets */, any() /* stableInsets */, + anyBoolean() /* reportDraw */, any() /* mergedConfig */, + any() /* backDropFrame */, anyBoolean() /* forceLayout */, + anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */, + any() /* displayCutout */); + } catch (RemoteException ignored) { + } + win.reportResized(); + win.updateResizingWindowIfNeeded(); + + // Even "resized" throws remote exception, it is still considered as reported. So the window + // shouldn't be resized again (which may block unfreeze in real case). + assertThat(mWm.mResizingWindows).doesNotContain(win); + assertFalse(win.getOrientationChanging()); + } + + @Test public void testGetTransformationMatrix() { final int PARENT_WINDOW_OFFSET = 1; final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 084216a9a543..fc95556750f1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -112,7 +112,7 @@ class WindowTestUtils { TestWindowState(WindowManagerService service, Session session, IWindow window, WindowManager.LayoutParams attrs, WindowToken token) { - super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, + super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0, false /* ownerCanAddInternalSystemWindow */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 1ca2e318b0d7..e561c13a4e99 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.Intent; +import android.os.UserHandle; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; @@ -134,6 +135,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY, "mChildAppWindowBelow"); + mDisplayContent.getDisplayPolicy().setForceShowSystemBars(false); } // Adding a display will cause freezing the display. Make sure to wait until it's @@ -295,12 +297,13 @@ class WindowTestsBase extends SystemServiceTestsBase { WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow) { - return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, - mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper()); + return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId), + ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow, + mSystemServicesTestRule.getPowerManagerWrapper()); } static WindowState createWindow(WindowState parent, int type, WindowToken token, - String name, int ownerId, boolean ownerCanAddInternalSystemWindow, + String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow, WindowManagerService service, Session session, IWindow iWindow, WindowState.PowerManagerWrapper powerManagerWrapper) { synchronized (service.mGlobalLock) { @@ -308,8 +311,8 @@ class WindowTestsBase extends SystemServiceTestsBase { attrs.setTitle(name); final WindowState w = new WindowState(service, session, iWindow, token, parent, - OP_NONE, - 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, + OP_NONE, 0, attrs, VISIBLE, ownerId, userId, + ownerCanAddInternalSystemWindow, powerManagerWrapper); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 7a347cb050be..535d53eeef71 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Process.INVALID_UID; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -28,6 +29,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import android.content.res.Configuration; import android.os.IBinder; import android.platform.test.annotations.Presubmit; @@ -133,6 +135,30 @@ public class WindowTokenTests extends WindowTestsBase { assertEquals(0, token.getWindowsCount()); } + @Test + public void testClearFixedRotationTransform() { + final WindowToken appToken = mAppWindow.mToken; + final WindowToken wallpaperToken = mWallpaperWindow.mToken; + final Configuration config = new Configuration(mDisplayContent.getConfiguration()); + final int originalRotation = config.windowConfiguration.getRotation(); + final int targetRotation = (originalRotation + 1) % 4; + + config.windowConfiguration.setRotation(targetRotation); + appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config); + wallpaperToken.linkFixedRotationTransform(appToken); + + // The window tokens should apply the rotation by the transformation. + assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation()); + assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation()); + + // The display doesn't rotate, the transformation will be canceled. + mAppWindow.mToken.clearFixedRotationTransform(null /* applyDisplayRotation */); + + // The window tokens should restore to the original rotation. + assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation()); + assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation()); + } + /** * Test that {@link WindowToken} constructor parameters is set with expectation. */ @@ -152,7 +178,7 @@ public class WindowTokenTests extends WindowTestsBase { token = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_TOAST, true /* persistOnEmpty */, mDisplayContent, true /* ownerCanManageAppTokens */, - true /* roundedCornerOverlay */, true /* fromClientToken */); + INVALID_UID, true /* roundedCornerOverlay */, true /* fromClientToken */); assertTrue(token.mRoundedCornerOverlay); assertTrue(token.mFromClientToken); } @@ -166,7 +192,7 @@ public class WindowTokenTests extends WindowTestsBase { public void testSurfaceCreatedForWindowToken() { final WindowToken fromClientToken = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_APPLICATION_OVERLAY, true /* persistOnEmpty */, - mDisplayContent, true /* ownerCanManageAppTokens */, + mDisplayContent, true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */, true /* fromClientToken */); assertNull(fromClientToken.mSurfaceControl); @@ -175,7 +201,7 @@ public class WindowTokenTests extends WindowTestsBase { final WindowToken nonClientToken = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_TOAST, true /* persistOnEmpty */, mDisplayContent, - true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */, + true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */, false /* fromClientToken */); assertNotNull(nonClientToken.mSurfaceControl); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 6c0f2b0cd278..edbdd4e94ac8 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -40,6 +40,9 @@ import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; import android.hardware.soundtrigger.SoundTriggerModule; import android.os.Binder; import android.os.DeadObjectException; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.RemoteException; @@ -113,6 +116,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private PowerSaveModeListener mPowerSaveModeListener; + // Handler to process call state changes will delay to allow time for the audio + // and sound trigger HALs to process the end of call notifications + // before we re enable pending recognition requests. + private final Handler mHandler; + private static final int MSG_CALL_STATE_CHANGED = 0; + private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000; + SoundTriggerHelper(Context context) { ArrayList <ModuleProperties> modules = new ArrayList<>(); int status = SoundTrigger.listModules(modules); @@ -130,6 +140,31 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // TODO: Figure out how to determine which module corresponds to the DSP hardware. mModuleProperties = modules.get(0); } + + Looper looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + if (looper != null) { + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_CALL_STATE_CHANGED: + synchronized (mLock) { + onCallStateChangedLocked( + TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1); + } + break; + default: + Slog.e(TAG, "unknown message in handler:" + msg.what); + break; + } + } + }; + } else { + mHandler = null; + } } /** @@ -227,6 +262,37 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return status; } + private int prepareForRecognition(ModelData modelData) { + if (mModule == null) { + mModule = SoundTrigger.attachModule(mModuleProperties.getId(), this, null); + if (mModule == null) { + Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module"); + return STATUS_ERROR; + } + } + // Load the model if it is not loaded. + if (!modelData.isModelLoaded()) { + // Before we try and load this model, we should first make sure that any other + // models that don't have an active recognition/dead callback are unloaded. Since + // there is a finite limit on the number of models that the hardware may be able to + // have loaded, we want to make sure there's room for our model. + stopAndUnloadDeadModelsLocked(); + int[] handle = new int[] { 0 }; + int status = mModule.loadSoundModel(modelData.getSoundModel(), handle); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "prepareForRecognition: loadSoundModel failed with status: " + status); + return status; + } + modelData.setHandle(handle[0]); + modelData.setLoaded(); + if (DBG) { + Slog.d(TAG, "prepareForRecognition: Sound model loaded with handle:" + handle[0]); + } + } + return STATUS_OK; + } + + /** * Starts recognition for the given sound model. A single routine for both keyphrase and * generic sound models. @@ -248,12 +314,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { Slog.w(TAG, "Attempting startRecognition without the capability"); return STATUS_ERROR; } - if (mModule == null) { - mModule = SoundTrigger.attachModule(mModuleProperties.getId(), this, null); - if (mModule == null) { - Slog.w(TAG, "startRecognition cannot attach to sound trigger module"); - return STATUS_ERROR; + + IRecognitionStatusCallback oldCallback = modelData.getCallback(); + if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) { + Slog.w(TAG, "Canceling previous recognition for model id: " + + modelData.getModelId()); + try { + oldCallback.onError(STATUS_ERROR); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetectionStopped", e); } + modelData.clearCallback(); } // If the existing SoundModel is different (for the same UUID for Generic and same @@ -287,48 +358,25 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } - IRecognitionStatusCallback oldCallback = modelData.getCallback(); - if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) { - Slog.w(TAG, "Canceling previous recognition for model id: " + - modelData.getModelId()); - try { - oldCallback.onError(STATUS_ERROR); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onDetectionStopped", e); - } - modelData.clearCallback(); - } - - // Load the model if it is not loaded. - if (!modelData.isModelLoaded()) { - // Before we try and load this model, we should first make sure that any other - // models that don't have an active recognition/dead callback are unloaded. Since - // there is a finite limit on the number of models that the hardware may be able to - // have loaded, we want to make sure there's room for our model. - stopAndUnloadDeadModelsLocked(); - int[] handle = new int[] { INVALID_VALUE }; - int status = mModule.loadSoundModel(soundModel, handle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "loadSoundModel call failed with " + status); - return status; - } - if (handle[0] == INVALID_VALUE) { - Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); - return STATUS_ERROR; - } - modelData.setHandle(handle[0]); - modelData.setLoaded(); - Slog.d(TAG, "Sound model loaded with handle:" + handle[0]); - } modelData.setCallback(callback); modelData.setRequested(true); modelData.setRecognitionConfig(recognitionConfig); modelData.setSoundModel(soundModel); - int status = startRecognitionLocked(modelData, + if (!isRecognitionAllowed()) { + initializeTelephonyAndPowerStateListeners(); + return STATUS_OK; + } + + int status = prepareForRecognition(modelData); + if (status != STATUS_OK) { + Slog.w(TAG, "startRecognition failed to prepare model for recognition"); + return status; + } + status = startRecognitionLocked(modelData, false /* Don't notify for synchronous calls */); - // Initialize power save, call active state monitoring logic. + // Initialize power save, call active state monitoring logic. if (status == STATUS_OK) { initializeTelephonyAndPowerStateListeners(); } @@ -398,7 +446,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (DBG) { Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" + callback.asBinder()); - Slog.d(TAG, "current callback=" + (modelData == null ? "null" : + Slog.d(TAG, "current callback=" + + ((modelData == null || modelData.getCallback() == null) ? "null" : modelData.getCallback().asBinder())); } int status = stopRecognition(modelData, callback); @@ -507,8 +556,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { synchronized (mLock) { MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1); ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); - if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE || - !modelData.isKeyphraseModel()) { + if (mModule == null || modelData == null || !modelData.isModelLoaded() + || !modelData.isKeyphraseModel()) { return STATUS_ERROR; } @@ -943,6 +992,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_OK; } if (start) { + int status = prepareForRecognition(model); + if (status != STATUS_OK) { + return status; + } return startRecognitionLocked(model, notify); } else { return stopRecognitionLocked(model, notify); @@ -992,8 +1045,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { @Override public void onCallStateChanged(int state, String arg1) { if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); - synchronized (mLock) { - onCallStateChangedLocked(TelephonyManager.CALL_STATE_OFFHOOK == state); + + if (mHandler != null) { + synchronized (mLock) { + mHandler.removeMessages(MSG_CALL_STATE_CHANGED); + Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0); + mHandler.sendMessageDelayed( + msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0 + : CALL_INACTIVE_MSG_DELAY_MS); + } } } } @@ -1216,9 +1276,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // models. private int startRecognitionLocked(ModelData modelData, boolean notify) { IRecognitionStatusCallback callback = modelData.getCallback(); - int handle = modelData.getHandle(); RecognitionConfig config = modelData.getRecognitionConfig(); - if (callback == null || handle == INVALID_VALUE || config == null) { + if (callback == null || !modelData.isModelLoaded() || config == null) { // Nothing to do here. Slog.w(TAG, "startRecognition: Bad data passed in."); MetricsLogger.count(mContext, "sth_start_recognition_error", 1); @@ -1235,7 +1294,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (mModule == null) { return STATUS_ERROR; } - int status = mModule.startRecognition(handle, config); + int status = mModule.startRecognition(modelData.getHandle(), config); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "startRecognition failed with " + status); MetricsLogger.count(mContext, "sth_start_recognition_error", 1); @@ -1376,7 +1435,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Model handle is an integer used by the HAL as an identifier for sound // models. - private int mModelHandle = INVALID_VALUE; + private int mModelHandle; // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel. private SoundModel mSoundModel = null; @@ -1436,7 +1495,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { synchronized void clearState() { mModelState = MODEL_NOTLOADED; - mModelHandle = INVALID_VALUE; mRecognitionConfig = null; mRequested = false; mCallback = null; diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index d5851d85345f..0c25cfb968fd 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -34,18 +34,21 @@ import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.BackgroundDexOptService; import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; import com.android.server.wm.ActivityTaskManagerInternal; +import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.HashMap; @@ -286,6 +289,7 @@ public class IorapForwardingService extends SystemService { private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator(); + private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated(); private boolean mRegisteredListeners = false; private void registerInProcessListenersLocked() { @@ -308,9 +312,22 @@ public class IorapForwardingService extends SystemService { launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); + BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated); + + mRegisteredListeners = true; } + private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener { + @Override + public void onPackagesUpdated(ArraySet<String> updatedPackages) { + String[] updated = updatedPackages.toArray(new String[0]); + for (String packageName : updated) { + Log.d(TAG, "onPackagesUpdated: " + packageName); + } + } + } + private class AppLaunchObserver implements ActivityMetricsLaunchObserver { // We add a synthetic sequence ID here to make it easier to differentiate new // launch sequences on the native side. diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index f019a9d33005..4e14fd3d59a1 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -74,6 +74,7 @@ public abstract class Conference extends Conferenceable { public void onConnectionEvent(Conference c, String event, Bundle extras) {} public void onCallerDisplayNameChanged( Conference c, String callerDisplayName, int presentation) {} + public void onCallDirectionChanged(Conference c, int callDirection) {} public void onRingbackRequested(Conference c, boolean ringback) {} } @@ -103,6 +104,7 @@ public abstract class Conference extends Conferenceable { private int mAddressPresentation; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; + private int mCallDirection; private boolean mRingbackRequested = false; private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @@ -1024,6 +1026,25 @@ public abstract class Conference extends Conferenceable { } /** + * Sets the call direction of this {@link Conference}. By default, all {@link Conference}s have + * a direction of {@link android.telecom.Call.Details.CallDirection#DIRECTION_UNKNOWN}. The + * direction of a {@link Conference} is only applicable to the case where + * {@link #setConferenceState(boolean)} has been set to {@code false}, otherwise the direction + * will be ignored. + * @param callDirection The direction of the conference. + * @hide + */ + @RequiresPermission(MODIFY_PHONE_STATE) + public final void setCallDirection(@Call.Details.CallDirection int callDirection) { + Log.d(this, "setDirection %d", callDirection); + mCallDirection = callDirection; + for (Listener l : mListeners) { + l.onCallDirectionChanged(this, callDirection); + } + } + + + /** * Sets the address of this {@link Conference}. Used when {@link #setConferenceState(boolean)} * is called to mark a conference temporarily as NOT a conference. * <p> @@ -1071,16 +1092,16 @@ public abstract class Conference extends Conferenceable { * This is applicable in two cases: * <ol> * <li>When {@link #setConferenceState(boolean)} is used to mark a conference as - * temporarily "not a conference"; we need to present the correct address in the in-call - * UI.</li> + * temporarily "not a conference"; we need to present the correct address presentation in + * the in-call UI.</li> * <li>When the conference is not hosted on the current device, we need to know the address - * information for the purpose of showing the original address to the user, as well as for - * logging to the call log.</li> + * presentation information for the purpose of showing the original address to the user, as + * well as for logging to the call log.</li> * </ol> - * @return The address of the conference, or {@code null} if not applicable. + * @return The address presentation of the conference. * @hide */ - public final int getAddressPresentation() { + public final @TelecomManager.Presentation int getAddressPresentation() { return mAddressPresentation; } @@ -1102,6 +1123,15 @@ public abstract class Conference extends Conferenceable { } /** + * @return The call direction of this conference. Only applicable when + * {@link #setConferenceState(boolean)} is set to false. + * @hide + */ + public final @Call.Details.CallDirection int getCallDirection() { + return mCallDirection; + } + + /** * Sets the caller display name (CNAP) of this {@link Conference}. Used when * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a * conference. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index ffd25c08a8ba..73296986d82e 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1554,6 +1554,14 @@ public abstract class ConnectionService extends Service { } @Override + public void onCallDirectionChanged(Conference c, int direction) { + String id = mIdByConference.get(c); + if (id != null) { + mAdapter.setCallDirection(id, direction); + } + } + + @Override public void onAddressChanged(Conference c, Uri newAddress, int presentation) { String id = mIdByConference.get(c); if (id != null) { @@ -1857,25 +1865,23 @@ public abstract class ConnectionService extends Service { mConferenceById.put(callId, conference); mIdByConference.put(conference, callId); conference.addListener(mConferenceListener); - ParcelableConference parcelableConference = new ParcelableConference( - request.getAccountHandle(), - conference.getState(), - conference.getConnectionCapabilities(), - conference.getConnectionProperties(), - Collections.<String>emptyList(), //connectionIds - conference.getVideoProvider() == null ? - null : conference.getVideoProvider().getInterface(), - conference.getVideoState(), - conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealtimeMillis(), - conference.getStatusHints(), - conference.getExtras(), - conference.getAddress(), - conference.getAddressPresentation(), - conference.getCallerDisplayName(), - conference.getCallerDisplayNamePresentation(), - conference.getDisconnectCause(), - conference.isRingbackRequested()); + ParcelableConference parcelableConference = new ParcelableConference.Builder( + request.getAccountHandle(), conference.getState()) + .setConnectionCapabilities(conference.getConnectionCapabilities()) + .setConnectionProperties(conference.getConnectionProperties()) + .setVideoAttributes(conference.getVideoProvider() == null + ? null : conference.getVideoProvider().getInterface(), + conference.getVideoState()) + .setConnectTimeMillis(conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealtimeMillis()) + .setStatusHints(conference.getStatusHints()) + .setExtras(conference.getExtras()) + .setAddress(conference.getAddress(), conference.getAddressPresentation()) + .setCallerDisplayName(conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation()) + .setDisconnectCause(conference.getDisconnectCause()) + .setRingbackRequested(conference.isRingbackRequested()) + .build(); if (conference.getState() != Connection.STATE_DISCONNECTED) { conference.setTelecomCallId(callId); mAdapter.setVideoProvider(callId, conference.getVideoProvider()); @@ -2476,23 +2482,25 @@ public abstract class ConnectionService extends Service { } } conference.setTelecomCallId(id); - ParcelableConference parcelableConference = new ParcelableConference( - conference.getPhoneAccountHandle(), - conference.getState(), - conference.getConnectionCapabilities(), - conference.getConnectionProperties(), - connectionIds, - conference.getVideoProvider() == null ? - null : conference.getVideoProvider().getInterface(), - conference.getVideoState(), - conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealtimeMillis(), - conference.getStatusHints(), - conference.getExtras(), - conference.getAddress(), - conference.getAddressPresentation(), - conference.getCallerDisplayName(), - conference.getCallerDisplayNamePresentation()); + ParcelableConference parcelableConference = new ParcelableConference.Builder( + conference.getPhoneAccountHandle(), conference.getState()) + .setConnectionCapabilities(conference.getConnectionCapabilities()) + .setConnectionProperties(conference.getConnectionProperties()) + .setConnectionIds(connectionIds) + .setVideoAttributes(conference.getVideoProvider() == null + ? null : conference.getVideoProvider().getInterface(), + conference.getVideoState()) + .setConnectTimeMillis(conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealtimeMillis()) + .setStatusHints(conference.getStatusHints()) + .setExtras(conference.getExtras()) + .setAddress(conference.getAddress(), conference.getAddressPresentation()) + .setCallerDisplayName(conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation()) + .setDisconnectCause(conference.getDisconnectCause()) + .setRingbackRequested(conference.isRingbackRequested()) + .setCallDirection(conference.getCallDirection()) + .build(); mAdapter.addConferenceCall(id, parcelableConference); mAdapter.setVideoProvider(id, conference.getVideoProvider()); diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index 8f273233044e..f8a6cf03934a 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -693,4 +693,20 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } } + + /** + * Sets the direction of a call. Setting a new direction of an existing call is usually only + * applicable during single caller emulation during conferencing, see + * {@link Conference#setConferenceState(boolean)} for more information. + * @param callId The identifier of the call. + * @param direction The new direction of the call. + */ + void setCallDirection(String callId, @Call.Details.CallDirection int direction) { + for (IConnectionServiceAdapter a : mAdapters) { + try { + a.setCallDirection(callId, direction, Log.getExternalSession()); + } catch (RemoteException e) { + } + } + } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 79ad51b92b81..6c1ea322e66e 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -76,6 +76,7 @@ final class ConnectionServiceAdapterServant { private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35; private static final int MSG_SET_CONFERENCE_STATE = 36; private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37; + private static final int MSG_SET_CALL_DIRECTION = 38; private final IConnectionServiceAdapter mDelegate; @@ -353,7 +354,7 @@ final class ConnectionServiceAdapterServant { case MSG_CONNECTION_SERVICE_FOCUS_RELEASED: mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/); break; - case MSG_SET_CONFERENCE_STATE: + case MSG_SET_CONFERENCE_STATE: { SomeArgs args = (SomeArgs) msg.obj; try { mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2, @@ -361,6 +362,17 @@ final class ConnectionServiceAdapterServant { } finally { args.recycle(); } + break; + } + case MSG_SET_CALL_DIRECTION: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setCallDirection((String) args.arg1, args.argi1, + (Session.Info) args.arg2); + } finally { + args.recycle(); + } + } } } }; @@ -670,6 +682,16 @@ final class ConnectionServiceAdapterServant { args.arg3 = sessionInfo; mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget(); } + + @Override + public void setCallDirection(String callId, int direction, + Session.Info sessionInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.argi1 = direction; + args.arg2 = sessionInfo; + mHandler.obtainMessage(MSG_SET_CALL_DIRECTION, args).sendToTarget(); + } }; public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 90b69a338c7e..1f8aafbca476 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.android.internal.telecom.IVideoProvider; @@ -32,25 +33,130 @@ import com.android.internal.telecom.IVideoProvider; */ public final class ParcelableConference implements Parcelable { - private PhoneAccountHandle mPhoneAccount; - private int mState; - private int mConnectionCapabilities; - private int mConnectionProperties; - private List<String> mConnectionIds; - private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + public static final class Builder { + private final PhoneAccountHandle mPhoneAccount; + private final int mState; + private int mConnectionCapabilities; + private int mConnectionProperties; + private List<String> mConnectionIds = Collections.emptyList(); + private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private IVideoProvider mVideoProvider; + private int mVideoState = VideoProfile.STATE_AUDIO_ONLY; + private StatusHints mStatusHints; + private Bundle mExtras; + private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private Uri mAddress; + private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN; + private String mCallerDisplayName; + private int mCallerDisplayNamePresentation = TelecomManager.PRESENTATION_UNKNOWN;; + private DisconnectCause mDisconnectCause; + private boolean mRingbackRequested; + private int mCallDirection = Call.Details.DIRECTION_UNKNOWN; + + public Builder( + PhoneAccountHandle phoneAccount, + int state) { + mPhoneAccount = phoneAccount; + mState = state; + } + + public Builder setDisconnectCause(DisconnectCause cause) { + mDisconnectCause = cause; + return this; + } + + public Builder setRingbackRequested(boolean requested) { + mRingbackRequested = requested; + return this; + } + + public Builder setCallerDisplayName(String callerDisplayName, + @TelecomManager.Presentation int callerDisplayNamePresentation) { + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + return this; + } + + public Builder setAddress(Uri address, + @TelecomManager.Presentation int addressPresentation) { + mAddress = address; + mAddressPresentation = addressPresentation; + return this; + } + + public Builder setExtras(Bundle extras) { + mExtras = extras; + return this; + } + + public Builder setStatusHints(StatusHints hints) { + mStatusHints = hints; + return this; + } + + public Builder setConnectTimeMillis(long connectTimeMillis, long connectElapsedTimeMillis) { + mConnectTimeMillis = connectTimeMillis; + mConnectElapsedTimeMillis = connectElapsedTimeMillis; + return this; + } + + public Builder setVideoAttributes(IVideoProvider provider, + @VideoProfile.VideoState int videoState) { + mVideoProvider = provider; + mVideoState = videoState; + return this; + } + + public Builder setConnectionIds(List<String> connectionIds) { + mConnectionIds = connectionIds; + return this; + } + + public Builder setConnectionProperties(int properties) { + mConnectionProperties = properties; + return this; + } + + public Builder setConnectionCapabilities(int capabilities) { + mConnectionCapabilities = capabilities; + return this; + } + + public Builder setCallDirection(int callDirection) { + mCallDirection = callDirection; + return this; + } + + public ParcelableConference build() { + return new ParcelableConference(mPhoneAccount, mState, mConnectionCapabilities, + mConnectionProperties, mConnectionIds, mVideoProvider, mVideoState, + mConnectTimeMillis, mConnectElapsedTimeMillis, mStatusHints, mExtras, mAddress, + mAddressPresentation, mCallerDisplayName, mCallerDisplayNamePresentation, + mDisconnectCause, mRingbackRequested, mCallDirection); + } + } + + + private final PhoneAccountHandle mPhoneAccount; + private final int mState; + private final int mConnectionCapabilities; + private final int mConnectionProperties; + private final List<String> mConnectionIds; + private final long mConnectTimeMillis; private final IVideoProvider mVideoProvider; private final int mVideoState; - private StatusHints mStatusHints; - private Bundle mExtras; - private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private final StatusHints mStatusHints; + private final Bundle mExtras; + private final long mConnectElapsedTimeMillis; private final Uri mAddress; private final int mAddressPresentation; private final String mCallerDisplayName; private final int mCallerDisplayNamePresentation; - private DisconnectCause mDisconnectCause; - private boolean mRingbackRequested; + private final DisconnectCause mDisconnectCause; + private final boolean mRingbackRequested; + private final int mCallDirection; - public ParcelableConference( + private ParcelableConference( PhoneAccountHandle phoneAccount, int state, int connectionCapabilities, @@ -67,31 +173,8 @@ public final class ParcelableConference implements Parcelable { String callerDisplayName, int callerDisplayNamePresentation, DisconnectCause disconnectCause, - boolean ringbackRequested) { - this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds, - videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, - statusHints, extras, address, addressPresentation, callerDisplayName, - callerDisplayNamePresentation); - mDisconnectCause = disconnectCause; - mRingbackRequested = ringbackRequested; - } - - public ParcelableConference( - PhoneAccountHandle phoneAccount, - int state, - int connectionCapabilities, - int connectionProperties, - List<String> connectionIds, - IVideoProvider videoProvider, - int videoState, - long connectTimeMillis, - long connectElapsedTimeMillis, - StatusHints statusHints, - Bundle extras, - Uri address, - int addressPresentation, - String callerDisplayName, - int callerDisplayNamePresentation) { + boolean ringbackRequested, + int callDirection) { mPhoneAccount = phoneAccount; mState = state; mConnectionCapabilities = connectionCapabilities; @@ -107,8 +190,9 @@ public final class ParcelableConference implements Parcelable { mAddressPresentation = addressPresentation; mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = callerDisplayNamePresentation; - mDisconnectCause = null; - mRingbackRequested = false; + mDisconnectCause = disconnectCause; + mRingbackRequested = ringbackRequested; + mCallDirection = callDirection; } @Override @@ -134,6 +218,8 @@ public final class ParcelableConference implements Parcelable { .append(mRingbackRequested) .append(", disconnectCause: ") .append(mDisconnectCause) + .append(", callDirection: ") + .append(mCallDirection) .toString(); } @@ -192,10 +278,15 @@ public final class ParcelableConference implements Parcelable { public boolean isRingbackRequested() { return mRingbackRequested; } + public int getHandlePresentation() { return mAddressPresentation; } + public int getCallDirection() { + return mCallDirection; + } + public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR = new Parcelable.Creator<ParcelableConference> () { @Override @@ -220,12 +311,13 @@ public final class ParcelableConference implements Parcelable { int callerDisplayNamePresentation = source.readInt(); DisconnectCause disconnectCause = source.readParcelable(classLoader); boolean isRingbackRequested = source.readInt() == 1; + int callDirection = source.readInt(); return new ParcelableConference(phoneAccount, state, capabilities, properties, connectionIds, videoCallProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, statusHints, extras, address, addressPresentation, callerDisplayName, callerDisplayNamePresentation, disconnectCause, - isRingbackRequested); + isRingbackRequested, callDirection); } @Override @@ -261,5 +353,6 @@ public final class ParcelableConference implements Parcelable { destination.writeInt(mCallerDisplayNamePresentation); destination.writeParcelable(mDisconnectCause, 0); destination.writeInt(mRingbackRequested ? 1 : 0); + destination.writeInt(mCallDirection); } } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 76640e036eeb..cad5b707a146 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -485,6 +485,11 @@ final class RemoteConnectionService { Session.Info sessionInfo) { // Do nothing } + + @Override + public void setCallDirection(String callId, int direction, Session.Info sessionInfo) { + // Do nothing + } }; private final ConnectionServiceAdapterServant mServant = diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index c993cfad1d05..b974c567008f 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1534,12 +1534,22 @@ public class TelecomManager { /** * Return the line 1 phone number for given phone account. * - * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE} + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_SMS READ_SMS}, + * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, + * or that the caller is the default SMS app for any API level. + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * for apps targeting SDK API level 29 and below. * * @param accountHandle The handle for the account retrieve a number for. * @return A string representation of the line 1 phone number. */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.READ_SMS, + android.Manifest.permission.READ_PHONE_NUMBERS + }, conditional = true) public String getLine1Number(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index 4f63e08abce6..3fd7f949cfe6 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -132,4 +132,6 @@ oneway interface IConnectionServiceAdapter { void resetConnectionTime(String callIdi, in Session.Info sessionInfo); void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo); + + void setCallDirection(String callId, int direction, in Session.Info sessionInfo); } diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f3e9de0d2688..3048ad7c1fb0 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -311,7 +311,7 @@ public final class LocationAccessPolicy { } // If the user or profile is current, permission is granted. // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid); } private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 327e8b344eeb..56f3c3ec0622 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1147,6 +1147,21 @@ public class CarrierConfigManager { "support_ims_conference_event_package_bool"; /** + * Determines whether processing of conference event package data received on a device other + * than the conference host is supported. + * <p> + * When a device A merges calls B and C into a conference it is considered the conference host + * and B and C are considered the conference peers. + * <p> + * When {@code true}, the conference peer will display the conference state if it receives + * conference event package data from the network. When {@code false}, the conference peer will + * ignore conference event package data received from the network. + * @hide + */ + public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL = + "support_ims_conference_event_package_on_peer_bool"; + + /** * Determines whether High Definition audio property is displayed in the dialer UI. * If {@code false}, remove the HD audio property from the connection so that HD audio related * UI is not displayed. If {@code true}, keep HD audio property as it is configured. @@ -1163,6 +1178,25 @@ public class CarrierConfigManager { "support_ims_conference_call_bool"; /** + * Determines whether the device will locally disconnect an IMS conference when the participant + * count drops to zero. When {@code true}, it is assumed the carrier does NOT disconnect a + * conference when the participant count drops to zero and that the device must do this by + * disconnecting the conference locally. When {@code false}, it is assumed that the carrier + * is responsible for disconnecting the conference when there are no longer any participants + * present. + * <p> + * Note: both {@link #KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL} and + * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} must be true for this configuration to + * have any effect. + * <p> + * Defaults to {@code false}, meaning the carrier network is responsible for disconnecting an + * empty IMS conference. + * @hide + */ + public static final String KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL = + "local_disconnect_empty_ims_conference_bool"; + + /** * Determines whether video conference calls are supported by a carrier. When {@code true}, * video calls can be merged into conference calls, {@code false} otherwiwse. * <p> @@ -3778,8 +3812,10 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); + sDefaults.putBoolean(KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false); sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false); sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5); diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java index c0ae99e89cb3..806bac0c32c9 100644 --- a/telephony/java/android/telephony/CbGeoUtils.java +++ b/telephony/java/android/telephony/CbGeoUtils.java @@ -128,6 +128,23 @@ public class CbGeoUtils { public String toString() { return "(" + lat + "," + lng + ")"; } + + /** + * @hide + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof LatLng)) { + return false; + } + + LatLng l = (LatLng) o; + return lat == l.lat && lng == l.lng; + } } /** @@ -280,6 +297,32 @@ public class CbGeoUtils { } return str; } + + /** + * @hide + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof Polygon)) { + return false; + } + + Polygon p = (Polygon) o; + if (mVertices.size() != p.mVertices.size()) { + return false; + } + for (int i = 0; i < mVertices.size(); i++) { + if (!mVertices.get(i).equals(p.mVertices.get(i))) { + return false; + } + } + + return true; + } } /** @@ -335,6 +378,24 @@ public class CbGeoUtils { return str; } + + /** + * @hide + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof Circle)) { + return false; + } + + Circle c = (Circle) o; + return mCenter.equals(c.mCenter) + && Double.compare(mRadiusMeter, c.mRadiusMeter) == 0; + } } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d6cdaa6d8bc0..f623649fb25e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -147,6 +147,10 @@ import java.util.regex.Pattern; * information unless it has the appropriate permissions declared in * its manifest file. Where permissions apply, they are noted in the * the methods through which you access the protected information. + * + * <p>TelephonyManager is intended for use on devices that implement + * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices + * that do not implement this feature, the behavior is not reliable. */ @SystemService(Context.TELEPHONY_SERVICE) public class TelephonyManager { @@ -5842,6 +5846,10 @@ public class TelephonyManager { * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()} * for each active subscription. * + * <p>This method returns valid data for devices with + * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices + * that do not implement this feature, the behavior is not reliable. + * * @param executor the executor on which callback will be invoked. * @param callback a callback to receive CellInfo. */ @@ -5888,6 +5896,10 @@ public class TelephonyManager { * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()} * for each active subscription. * + * <p>This method returns valid data for devices with + * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices + * that do not implement this feature, the behavior is not reliable. + * * @param workSource the requestor to whom the power consumption for this should be attributed. * @param executor the executor on which callback will be invoked. * @param callback a callback to receive CellInfo. diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 2b1d9e58c4d5..18e25921555a 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -113,6 +113,7 @@ public class DctConstants { public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53; public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54; public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55; + public static final int EVENT_SIM_STATE_UPDATED = BASE + 56; /***** Constants *****/ diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp index 248206817740..02c75edafcd5 100644 --- a/tests/ApkVerityTest/Android.bp +++ b/tests/ApkVerityTest/Android.bp @@ -16,7 +16,7 @@ java_test_host { name: "ApkVerityTest", srcs: ["src/**/*.java"], libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], - test_suites: ["general-tests", "vts-core"], + test_suites: ["general-tests", "vts"], target_required: [ "block_device_writer_module", ], diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp index 2760fe8df2d8..37fbc29470f6 100644 --- a/tests/ApkVerityTest/block_device_writer/Android.bp +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -48,6 +48,6 @@ cc_test { }, auto_gen_config: false, - test_suites: ["general-tests", "pts", "vts-core"], + test_suites: ["general-tests", "pts", "vts"], gtest: false, } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 13bf17954aa4..2d2f4dbdf907 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -107,7 +107,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save private static final int IORAP_TRACE_DURATION_TIMEOUT = 7000; // Allow 7s for trace to complete. private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 3; // min 3 launches to merge traces. - private static final int IORAP_COMPILE_CMD_TIMEOUT = 600; // in seconds: 10 minutes + private static final int IORAP_COMPILE_CMD_TIMEOUT = 60; // in seconds: 1 minutes + private static final int IORAP_COMPILE_MIN_TRACES = 1; // configure iorapd to need 1 trace. + private static final int IORAP_COMPILE_RETRIES = 3; // retry compiler 3 times if it fails. private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; private static final String LAUNCH_FILE = "applaunch.txt"; private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; @@ -132,9 +134,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; - private static final String IORAP_COMPILE_CMD = "cmd jobscheduler run -f android 283673059"; + private static final String IORAP_COMPILE_CMD = "dumpsys iorapd --compile-package %s"; private static final String IORAP_MAINTENANCE_CMD = - "iorap.cmd.maintenance --purge-package %s /data/misc/iorapd/sqlite.db"; + "dumpsys iorapd --purge-package %s"; private static final String IORAP_DUMPSYS_CMD = "dumpsys iorapd"; private static final String SPEED_PROFILE_FILTER = "speed-profile"; private static final String VERIFY_FILTER = "verify"; @@ -350,9 +352,9 @@ public class AppLaunch extends InstrumentationTestCase { sleep(IORAP_TRACE_DURATION_TIMEOUT); if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_LAST)) { - // run the iorap job scheduler and wait for iorap to compile fully. - assertTrue(String.format("Not able to iorap-compile the app : %s", appPkgName), - compileAppForIorap(appPkgName)); + // run the iorap compiler and wait for iorap to compile fully. + // this throws an exception if it fails. + compileAppForIorapWithRetries(appPkgName, IORAP_COMPILE_RETRIES); } } @@ -506,6 +508,22 @@ public class AppLaunch extends InstrumentationTestCase { } /** + * Compile the app package using compilerFilter, + * retrying if the compilation command fails in between. + */ + private void compileAppForIorapWithRetries(String appPkgName, int retries) throws IOException { + for (int i = 0; i < retries; ++i) { + if (compileAppForIorap(appPkgName)) { + return; + } + sleep(1000); + } + + throw new IllegalStateException("compileAppForIorapWithRetries: timed out after " + + retries + " retries"); + } + + /** * Compile the app package using compilerFilter and return true or false * based on status of the compilation command. */ @@ -513,7 +531,7 @@ public class AppLaunch extends InstrumentationTestCase { String logcatTimestamp = getTimeNowForLogcat(); getInstrumentation().getUiAutomation(). - executeShellCommand(IORAP_COMPILE_CMD); + executeShellCommand(String.format(IORAP_COMPILE_CMD, appPkgName)); int i = 0; for (i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) { @@ -525,7 +543,8 @@ public class AppLaunch extends InstrumentationTestCase { } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) { Log.e(TAG, "compileAppForIorap: failed due to insufficient traces"); logDumpsysIorapd(appPkgName); - return false; + throw new IllegalStateException( + "compileAppForIorap: failed due to insufficient traces"); } // else INCOMPLETE. keep asking iorapd if it's done yet. sleep(1000); } @@ -536,19 +555,7 @@ public class AppLaunch extends InstrumentationTestCase { return false; } - // Wait for the job to finish completely. - // Other packages could be compiled in cyclic runs. - int currentAttempt = 0; - do { - String logcatLines = getLogcatSinceTime(logcatTimestamp); - if (logcatLines.contains("IorapForwardingService: Finished background job")) { - return true; - } - sleep(1000); - } while (currentAttempt++ < IORAP_COMPILE_CMD_TIMEOUT); - - Log.e(TAG, "compileAppForIorap: failed due to jobscheduler timeout."); - return false; + return true; } /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */ @@ -808,11 +815,9 @@ public class AppLaunch extends InstrumentationTestCase { } Log.v(TAG, "Purge iorap package: " + packageName); - stopIorapd(); getInstrumentation().getUiAutomation() .executeShellCommand(String.format(IORAP_MAINTENANCE_CMD, packageName)); Log.v(TAG, "Executed: " + String.format(IORAP_MAINTENANCE_CMD, packageName)); - startIorapd(); } String executeShellCommandWithTempFile(String cmd) { @@ -892,12 +897,16 @@ public class AppLaunch extends InstrumentationTestCase { throw new AssertionError(e); } - stopIorapd(); getInstrumentation().getUiAutomation() .executeShellCommand(String.format("setprop iorapd.perfetto.enable %b", enable)); getInstrumentation().getUiAutomation() .executeShellCommand(String.format("setprop iorapd.readahead.enable %b", enable)); - startIorapd(); + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format( + "setprop iorapd.maintenance.min_traces %d", IORAP_COMPILE_MIN_TRACES)); + // this last command blocks until iorapd refreshes its system properties + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format("dumpsys iorapd --refresh-properties")); if (enable) { mIorapStatus = IorapStatus.ENABLED; diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java index b40d022f075d..86c3fa0fe034 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -53,8 +53,9 @@ public class TaskOrganizerMultiWindowTest extends Activity { return true; } - float x = e.getX(0); + float x = e.getRawX(0); float ratio = (float) x / (float) getWidth() ; + ratio = 1-ratio; LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(0, @@ -172,10 +173,14 @@ public class TaskOrganizerMultiWindowTest extends Activity { setContentView(splitView); } + private void addFlags(Intent intent) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); + } + Intent makeSettingsIntent() { Intent intent = new Intent(); intent.setAction(android.provider.Settings.ACTION_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + addFlags(intent); return intent; } @@ -183,7 +188,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { Intent intent = new Intent(); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_APP_CONTACTS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + addFlags(intent); return intent; } diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java index 03615f332723..aa041f22a46e 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java @@ -80,8 +80,10 @@ class TaskView extends SurfaceView implements SurfaceHolder.Callback { } catch (Exception e) { // System server died.. oh well } + t.reparent(leash, getSurfaceControl()) .setPosition(leash, 0, 0) + .show(leash) .apply(); } } diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java index 548af0c54b03..498cb7c1c710 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -18,7 +18,6 @@ package com.google.android.test.windowinsetstests; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; - import static java.lang.Math.max; import static java.lang.Math.min; @@ -31,6 +30,7 @@ import android.content.Context; import android.graphics.Insets; import android.os.Bundle; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -44,11 +44,11 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; -import androidx.appcompat.app.AppCompatActivity; - import java.util.ArrayList; import java.util.List; +import androidx.appcompat.app.AppCompatActivity; + public class WindowInsetsActivity extends AppCompatActivity { private View mRoot; @@ -191,6 +191,40 @@ public class WindowInsetsActivity extends AppCompatActivity { mTransitions.forEach(it -> it.onFinish(animation)); } }); + + findViewById(R.id.floating_action_button).setOnClickListener( + v -> v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), -1, + new LinearInterpolator(), null /* cancellationSignal */, + new WindowInsetsAnimationControlListener() { + @Override + public void onReady( + WindowInsetsAnimationController controller, + int types) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(1500); + anim.addUpdateListener(animation + -> controller.setInsetsAndAlpha( + controller.getShownStateInsets(), + (float) animation.getAnimatedValue(), + anim.getAnimatedFraction())); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.finish(true); + } + }); + anim.start(); + } + + @Override + public void onCancelled(WindowInsetsAnimationController controller) { + } + + @Override + public void onFinished(WindowInsetsAnimationController controller) { + } + })); } @Override @@ -200,57 +234,6 @@ public class WindowInsetsActivity extends AppCompatActivity { getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - getWindow().getInsetsController().addOnControllableInsetsChangedListener( - new OnControllableInsetsChangedListener() { - - boolean hasControl = false; - @Override - public void onControllableInsetsChanged(WindowInsetsController controller, - int types) { - if ((types & ime()) != 0 && !hasControl) { - hasControl = true; - controller.controlWindowInsetsAnimation(ime(), -1, - new LinearInterpolator(), null /* cancellationSignal */, - new WindowInsetsAnimationControlListener() { - @Override - public void onReady( - WindowInsetsAnimationController controller, - int types) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setDuration(1500); - anim.addUpdateListener(animation - -> controller.setInsetsAndAlpha( - controller.getShownStateInsets(), - (float) animation.getAnimatedValue(), - anim.getAnimatedFraction())); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - controller.finish(true); - } - }); - anim.start(); - } - - @Override - public void onFinished( - WindowInsetsAnimationController controller) { - } - - @Override - public void onCancelled( - WindowInsetsAnimationController controller) { - } - }); - } - } - }); - } - static class Transition { private int mEndBottom; private int mStartBottom; diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java new file mode 100644 index 000000000000..ac1c28a45462 --- /dev/null +++ b/tests/net/common/java/android/net/DependenciesTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * A simple class that tests dependencies to java standard tools from the + * Network stack. These tests are not meant to be comprehensive tests of + * the relevant APIs : such tests belong in the relevant test suite for + * these dependencies. Instead, this just makes sure coverage is present + * by calling the methods in the exact way (or a representative way of how) + * they are called in the network stack. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DependenciesTest { + // Used to in ipmemorystore's RegularMaintenanceJobService to convert + // 24 hours into seconds + @Test + public void testTimeUnit() { + final int hours = 24; + final long inSeconds = TimeUnit.HOURS.toMillis(hours); + assertEquals(inSeconds, hours * 60 * 60 * 1000); + } + + private byte[] makeTrivialArray(final int size) { + final byte[] src = new byte[size]; + for (int i = 0; i < size; ++i) { + src[i] = (byte) i; + } + return src; + } + + // Used in ApfFilter to find an IP address from a byte array + @Test + public void testArrays() { + final int size = 128; + final byte[] src = makeTrivialArray(size); + + // Test copy + final int copySize = 16; + final int offset = 24; + final byte[] expected = new byte[copySize]; + for (int i = 0; i < copySize; ++i) { + expected[i] = (byte) (offset + i); + } + + final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize); + assertArrayEquals(expected, copy); + assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size)); + } + + // Used mainly in the Dhcp code + @Test + public void testCopyOf() { + final byte[] src = makeTrivialArray(128); + final byte[] copy = Arrays.copyOf(src, src.length); + assertArrayEquals(src, copy); + assertFalse(src == copy); + + assertArrayEquals(new byte[0], Arrays.copyOf(src, 0)); + + final int excess = 16; + final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess); + for (int i = src.length; i < src.length + excess; ++i) { + assertEquals(0, biggerCopy[i]); + } + for (int i = src.length - 1; i >= 0; --i) { + assertEquals(src[i], biggerCopy[i]); + } + } + + // Used mainly in DnsUtils but also various other places + @Test + public void testAsList() { + final int size = 24; + final Object[] src = new Object[size]; + final ArrayList<Object> expected = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + final Object o = new Object(); + src[i] = o; + expected.add(o); + } + assertEquals(expected, Arrays.asList(src)); + } +} diff --git a/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt new file mode 100644 index 000000000000..ef15b668e24c --- /dev/null +++ b/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.net.wifi.aware.DiscoverySession +import android.net.wifi.aware.PeerHandle +import android.net.wifi.aware.WifiAwareNetworkSpecifier +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 + +import com.android.testutils.assertParcelSane + +import java.lang.IllegalStateException + +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MatchAllNetworkSpecifierTest { + @Test + fun testParcel() { + assertParcelSane(MatchAllNetworkSpecifier(), 0) + } + + @Test(expected = IllegalStateException::class) + fun testSatisfiedBy() { + val specifier = MatchAllNetworkSpecifier() + val discoverySession = Mockito.mock(DiscoverySession::class.java) + val peerHandle = Mockito.mock(PeerHandle::class.java) + val wifiAwareNetworkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, + peerHandle).build() + specifier.satisfiedBy(wifiAwareNetworkSpecifier) + } +} diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 316a83adf45e..3f8261d5ad7f 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -17,6 +17,8 @@ package android.net; import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; @@ -32,10 +34,12 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVIT import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; +import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; import static com.android.testutils.ParcelUtilsKt.assertParcelSane; @@ -45,18 +49,28 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.wifi.aware.DiscoverySession; +import android.net.wifi.aware.PeerHandle; +import android.net.wifi.aware.WifiAwareNetworkSpecifier; import android.os.Build; +import android.os.Process; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; import androidx.core.os.BuildCompat; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.util.Arrays; import java.util.Set; @@ -67,6 +81,12 @@ public class NetworkCapabilitiesTest { private static final String TEST_SSID = "TEST_SSID"; private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID"; + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + + private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class); + private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class); + private boolean isAtLeastR() { // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R. // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after @@ -441,7 +461,7 @@ public class NetworkCapabilitiesTest { return range; } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testSetAdministratorUids() { NetworkCapabilities nc = new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3}); @@ -449,7 +469,7 @@ public class NetworkCapabilitiesTest { assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids()); } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testSetAdministratorUidsWithDuplicates() { try { new NetworkCapabilities().setAdministratorUids(new int[] {1, 1}); @@ -510,6 +530,12 @@ public class NetworkCapabilitiesTest { assertFalse(nc2.appliesToUid(12)); assertTrue(nc1.appliesToUid(22)); assertTrue(nc2.appliesToUid(22)); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCombineCapabilities_AdministratorUids() { + final NetworkCapabilities nc1 = new NetworkCapabilities(); + final NetworkCapabilities nc2 = new NetworkCapabilities(); final int[] adminUids = {3, 6, 12}; nc1.setAdministratorUids(adminUids); @@ -518,7 +544,7 @@ public class NetworkCapabilitiesTest { assertArrayEquals(nc2.getAdministratorUids(), adminUids); final int[] adminUidsOtherOrder = {3, 12, 6}; - nc1.setAdministratorUids(adminUids); + nc1.setAdministratorUids(adminUidsOtherOrder); assertTrue(nc2.equalsAdministratorUids(nc1)); final int[] adminUids2 = {11, 1, 12, 3, 6}; @@ -672,4 +698,238 @@ public class NetworkCapabilitiesTest { assertEquals(TRANSPORT_VPN, transportTypes[2]); assertEquals(TRANSPORT_TEST, transportTypes[3]); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testTelephonyNetworkSpecifier() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(specifier) + .build(); + assertEquals(specifier, nc1.getNetworkSpecifier()); + try { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setNetworkSpecifier(specifier) + .build(); + fail("Must have a single transport type. Without transport type or multiple transport" + + " types is invalid."); + } catch (IllegalStateException expected) { } + } + + @Test + public void testWifiAwareNetworkSpecifier() { + final NetworkCapabilities nc = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI_AWARE); + // If NetworkSpecifier is not set, the default value is null. + assertNull(nc.getNetworkSpecifier()); + final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder( + mDiscoverySession, mPeerHandle).build(); + nc.setNetworkSpecifier(specifier); + assertEquals(specifier, nc.getNetworkSpecifier()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testAdministratorUidsAndOwnerUid() { + // Test default owner uid. + // If the owner uid is not set, the default value should be Process.INVALID_UID. + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build(); + assertEquals(Process.INVALID_UID, nc1.getOwnerUid()); + // Test setAdministratorUids and getAdministratorUids. + final int[] administratorUids = {1001, 10001}; + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .build(); + assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids())); + // Test setOwnerUid and getOwnerUid. + // The owner UID must be included in administrator UIDs, or throw IllegalStateException. + try { + final NetworkCapabilities nc3 = new NetworkCapabilities.Builder() + .setOwnerUid(1001) + .build(); + fail("The owner UID must be included in administrator UIDs."); + } catch (IllegalStateException expected) { } + final NetworkCapabilities nc4 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .setOwnerUid(1001) + .build(); + assertEquals(1001, nc4.getOwnerUid()); + try { + final NetworkCapabilities nc5 = new NetworkCapabilities.Builder() + .setAdministratorUids(null) + .build(); + fail("Should not set null into setAdministratorUids"); + } catch (NullPointerException expected) { } + } + + @Test + public void testLinkBandwidthKbps() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED. + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps()); + nc.setLinkDownstreamBandwidthKbps(512); + nc.setLinkUpstreamBandwidthKbps(128); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + } + + @Test + public void testSignalStrength() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED. + assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength()); + nc.setSignalStrength(-80); + assertEquals(-80, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDeduceRestrictedCapability() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // Default capabilities don't have restricted capability. + assertFalse(nc.deduceRestrictedCapability()); + // If there is a force restricted capability, then the network capabilities is restricted. + nc.addCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, if there is any unrestricted capability in + // capabilities, then the network capabilities is not restricted. + nc.removeCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_CBS); + assertFalse(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, the network capabilities will only be treated + // as restricted when there is no any unrestricted capability. + nc.removeCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + } + + private void assertNoTransport(NetworkCapabilities nc) { + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + assertFalse(nc.hasTransport(i)); + } + } + + // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all + // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence + // is true. If positiveSequence is false, then the check sequence is opposite. + private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType, + boolean positiveSequence) { + for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) { + if (positiveSequence) { + assertTrue(nc.hasTransport(i)); + } else { + assertFalse(nc.hasTransport(i)); + } + } + for (int i = MAX_TRANSPORT; i > maxTransportType; i--) { + if (positiveSequence) { + assertFalse(nc.hasTransport(i)); + } else { + assertTrue(nc.hasTransport(i)); + } + } + } + + @Test + public void testMultipleTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + assertNoTransport(nc); + // Test adding multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.addTransportType(i); + checkCurrentTransportTypes(nc, i, true /* positiveSequence */); + } + // Test removing multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.removeTransportType(i); + checkCurrentTransportTypes(nc, i, false /* positiveSequence */); + } + assertNoTransport(nc); + nc.addTransportType(TRANSPORT_WIFI); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + nc.addTransportType(TRANSPORT_VPN); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_WIFI); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_VPN); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + assertNoTransport(nc); + } + + @Test + public void testAddAndRemoveTransportType() { + final NetworkCapabilities nc = new NetworkCapabilities(); + try { + nc.addTransportType(-1); + fail("Should not set invalid transport type into addTransportType"); + } catch (IllegalArgumentException expected) { } + try { + nc.removeTransportType(-1); + fail("Should not set invalid transport type into removeTransportType"); + } catch (IllegalArgumentException e) { } + } + + private class TestTransportInfo implements TransportInfo { + TestTransportInfo() { + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testBuilder() { + final int ownerUid = 1001; + final int signalStrength = -80; + final int requestUid = 10100; + final int[] administratorUids = {ownerUid, 10001}; + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final TestTransportInfo transportInfo = new TestTransportInfo(); + final String ssid = "TEST_SSID"; + final String packageName = "com.google.test.networkcapabilities"; + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .removeTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_CBS) + .removeCapability(NET_CAPABILITY_CBS) + .setAdministratorUids(administratorUids) + .setOwnerUid(ownerUid) + .setLinkDownstreamBandwidthKbps(512) + .setLinkUpstreamBandwidthKbps(128) + .setNetworkSpecifier(specifier) + .setTransportInfo(transportInfo) + .setSignalStrength(signalStrength) + .setSsid(ssid) + .setRequestorUid(requestUid) + .setRequestorPackageName(packageName) + .build(); + assertEquals(1, nc.getTransportTypes().length); + assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]); + assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS)); + assertFalse(nc.hasCapability(NET_CAPABILITY_CBS)); + assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids())); + assertEquals(ownerUid, nc.getOwnerUid()); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + assertEquals(specifier, nc.getNetworkSpecifier()); + assertEquals(transportInfo, nc.getTransportInfo()); + assertEquals(signalStrength, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + assertEquals(ssid, nc.getSsid()); + assertEquals(requestUid, nc.getRequestorUid()); + assertEquals(packageName, nc.getRequestorPackageName()); + // Cannot assign null into NetworkCapabilities.Builder + try { + final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null); + fail("Should not set null into NetworkCapabilities.Builder"); + } catch (NullPointerException expected) { } + assertEquals(nc, new NetworkCapabilities.Builder(nc).build()); + } } diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt new file mode 100644 index 000000000000..7b22e45db90a --- /dev/null +++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.netstats + +import android.net.NetworkStats +import android.net.NetworkStats.DEFAULT_NETWORK_NO +import android.net.NetworkStats.DEFAULT_NETWORK_YES +import android.net.NetworkStats.Entry +import android.net.NetworkStats.IFACE_VT +import android.net.NetworkStats.METERED_NO +import android.net.NetworkStats.METERED_YES +import android.net.NetworkStats.ROAMING_NO +import android.net.NetworkStats.ROAMING_YES +import android.net.NetworkStats.SET_DEFAULT +import android.net.NetworkStats.SET_FOREGROUND +import android.net.NetworkStats.TAG_NONE +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertNetworkStatsEquals +import com.android.testutils.assertParcelingIsLossless +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals + +@RunWith(JUnit4::class) +@SmallTest +class NetworkStatsApiTest { + @Rule + @JvmField + val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q) + + private val testStatsEmpty = NetworkStats(0L, 0) + + // Note that these variables need to be initialized outside of constructor, initialize + // here with methods that don't exist in Q devices will result in crash. + + // stats1 and stats2 will have some entries with common keys, which are expected to + // be merged if performing add on these 2 stats. + private lateinit var testStats1: NetworkStats + private lateinit var testStats2: NetworkStats + + // This is a result of adding stats1 and stats2, while the merging of common key items is + // subject to test later, this should not be initialized with for a loop to add stats1 + // and stats2 above. + private lateinit var testStats3: NetworkStats + + companion object { + private const val TEST_IFACE = "test0" + private const val TEST_UID1 = 1001 + private const val TEST_UID2 = 1002 + } + + @Before + fun setUp() { + testStats1 = NetworkStats(0L, 0) + // Entries which only appear in set1. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4)) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0)) + assertEquals(8, testStats1.size()) + + testStats2 = NetworkStats(0L, 0) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0)) + // Entry which only appears in set2. + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + assertEquals(5, testStats2.size()) + + testStats3 = NetworkStats(0L, 9) + // Entries which are unique either in stats1 or stats2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + // Entries which are common for stats1 and stats2 are being merged. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0)) + assertEquals(9, testStats3.size()) + } + + @Test + fun testAddEntry() { + val expectedEntriesInStats2 = arrayOf( + Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1), + Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45), + Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7), + Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0), + Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + + // While testStats* are already initialized with addEntry, verify content added + // matches expectation. + for (i in expectedEntriesInStats2.indices) { + val entry = testStats2.getValues(i, null) + assertEquals(expectedEntriesInStats2[i], entry) + } + + // Verify entry updated with addEntry. + val stats = testStats2.addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9)) + assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9), + stats.getValues(3, null)) + } + + @Test + fun testAdd() { + var stats = NetworkStats(0L, 0) + assertNetworkStatsEquals(testStatsEmpty, stats) + stats = stats.add(testStats2) + assertNetworkStatsEquals(testStats2, stats) + stats = stats.add(testStats1) + // EMPTY + STATS2 + STATS1 = STATS3 + assertNetworkStatsEquals(testStats3, stats) + } + + @Test + fun testParcelUnparcel() { + assertParcelingIsLossless(testStatsEmpty) + assertParcelingIsLossless(testStats1) + assertParcelingIsLossless(testStats2) + assertFieldCountEquals(15, NetworkStats::class.java) + } + + @Test + fun testDescribeContents() { + assertEquals(0, testStatsEmpty.describeContents()) + assertEquals(0, testStats1.describeContents()) + assertEquals(0, testStats2.describeContents()) + assertEquals(0, testStats3.describeContents()) + } + + @Test + fun testSubtract() { + // STATS3 - STATS2 = STATS1 + assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2)) + // STATS3 - STATS1 = STATS2 + assertNetworkStatsEquals(testStats2, testStats3.subtract(testStats1)) + } + + @Test + fun testMethodsDontModifyReceiver() { + listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach { + val origStats = it.clone() + it.addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + it.add(testStats3) + it.subtract(testStats1) + assertNetworkStatsEquals(origStats, it) + } + } +}
\ No newline at end of file diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index e71d5995255d..98f705f45e98 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -503,6 +503,53 @@ public class NetworkStatsTest { } @Test + public void testRemoveEmptyEntries() throws Exception { + // Test empty stats. + final NetworkStats statsEmpty = new NetworkStats(TEST_START, 3); + assertEquals(0, statsEmpty.removeEmptyEntries().size()); + + // Test stats with non-zero entry. + final NetworkStats statsNonZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + assertEquals(1, statsNonZero.size()); + final NetworkStats expectedNonZero = statsNonZero.removeEmptyEntries(); + assertEquals(1, expectedNonZero.size()); + assertValues(expectedNonZero, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + + // Test stats with empty entry. + final NetworkStats statsZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + assertEquals(1, statsZero.size()); + final NetworkStats expectedZero = statsZero.removeEmptyEntries(); + assertEquals(1, statsZero.size()); // Assert immutable. + assertEquals(0, expectedZero.size()); + + // Test stats with multiple entries. + final NetworkStats statsMultiple = new NetworkStats(TEST_START, 0) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 0L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 4L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 2L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 1L); + assertEquals(9, statsMultiple.size()); + final NetworkStats expectedMultiple = statsMultiple.removeEmptyEntries(); + assertEquals(9, statsMultiple.size()); // Assert immutable. + assertEquals(7, expectedMultiple.size()); + assertValues(expectedMultiple.getTotalIncludingTags(null), 14L, 104L, 4L, 4L, 21L); + + // Test stats with multiple empty entries. + assertEquals(statsMultiple.size(), statsMultiple.subtract(statsMultiple).size()); + assertEquals(0, statsMultiple.subtract(statsMultiple).removeEmptyEntries().size()); + } + + @Test public void testClone() throws Exception { final NetworkStats original = new NetworkStats(TEST_START, 5) .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index 9b248878fe96..d0ebb5283f49 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -181,7 +181,7 @@ public class Nat464XlatTest { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); // Start clat. nat.start(); @@ -222,7 +222,7 @@ public class Nat464XlatTest { ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); InOrder inOrder = inOrder(mNetd, mConnectivity); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -309,7 +309,7 @@ public class Nat464XlatTest { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -348,7 +348,7 @@ public class Nat464XlatTest { public void testStopBeforeClatdStarts() throws Exception { Nat464Xlat nat = makeNat464Xlat(); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -380,7 +380,7 @@ public class Nat464XlatTest { public void testStopAndClatdNeverStarts() throws Exception { Nat464Xlat nat = makeNat464Xlat(); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 4c2a984f8198..737665fb97e4 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -17,6 +17,7 @@ package com.android.framework.permission.tests; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import android.os.Binder; import android.os.RemoteException; @@ -27,6 +28,8 @@ import android.view.IWindowManager; import junit.framework.TestCase; +import org.junit.Test; + /** * TODO: Remove this. This is only a placeholder, need to implement this. */ @@ -53,7 +56,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addWindowToken(null, 0, DEFAULT_DISPLAY); + mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY); fail("IWindowManager.addWindowToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { @@ -63,16 +66,6 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.removeWindowToken(null, DEFAULT_DISPLAY); - fail("IWindowManager.removeWindowToken did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } catch (RemoteException e) { - fail("Unexpected remote exception"); - } - - try { mWm.prepareAppTransition(0, false); fail("IWindowManager.prepareAppTransition did not throw SecurityException as" + " expected"); @@ -182,4 +175,29 @@ public class WindowManagerPermissionTests extends TestCase { fail("Unexpected remote exception"); } } + + @Test + public void testADD_WINDOW_TOKEN_WITH_OPTIONS() { + // Verify if addWindowTokenWithOptions throw SecurityException for privileged window type. + try { + mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, ""); + fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + + // Verify if addWindowTokenWithOptions throw SecurityException for null packageName. + try { + mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, null); + fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + } } diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 3026e0b51133..0dd45bad8a01 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -47,7 +47,8 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.InsetsSourceConsumerTest", "android.view.InsetsStateTest", "android.view.WindowMetricsTest", - "android.view.PendingInsetsControllerTest" + "android.view.PendingInsetsControllerTest", + "android.app.WindowContextTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index d56f2be7ecb3..b513463ec98f 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -160,7 +160,7 @@ struct AtomDecl { int exclusiveField = 0; int defaultState = INT_MAX; int triggerStateReset = INT_MAX; - bool nested; + bool nested = true; int uidField = 0; diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp index 23a0f7278271..5fe94987aa65 100644 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ b/tools/stats_log_api_gen/atoms_info_writer.cpp @@ -42,7 +42,6 @@ static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { fprintf(out, " const static std::set<int> " "kTruncatingTimestampAtomBlackList;\n"); - fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n"); fprintf(out, " const static std::set<int> kAtomsWithAttributionChain;\n"); fprintf(out, " const static std::map<int, StateAtomFieldOptions> " @@ -101,28 +100,6 @@ static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) { fprintf(out, "};\n"); fprintf(out, "\n"); - fprintf(out, "static std::map<int, int> getAtomUidField() {\n"); - fprintf(out, " std::map<int, int> uidField;\n"); - for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end(); - atomIt++) { - if ((*atomIt)->uidField == 0) { - continue; - } - fprintf(out, - "\n // Adding uid field for atom " - "(%d)%s\n", - (*atomIt)->code, (*atomIt)->name.c_str()); - fprintf(out, " uidField[%d /* %s */] = %d;\n", (*atomIt)->code, - make_constant_name((*atomIt)->name).c_str(), (*atomIt)->uidField); - } - - fprintf(out, " return uidField;\n"); - fprintf(out, "};\n"); - - fprintf(out, - "const std::map<int, int> AtomsInfo::kAtomsWithUidField = " - "getAtomUidField();\n"); - fprintf(out, "static std::map<int, StateAtomFieldOptions> " "getStateAtomFieldOptions() {\n"); diff --git a/wifi/Android.bp b/wifi/Android.bp index 2029479fb09f..99beb7bfa043 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -81,7 +81,6 @@ java_library { libs: [ "framework-annotations-lib", "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage - "framework-telephony-stubs", ], srcs: [ ":framework-wifi-updatable-sources", diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index eeb006ee6ab2..f0333c98b8a7 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -10,11 +10,17 @@ rule android.net.NetworkFactory* com.android.server.x.wifi.net.NetworkFactory@1 rule android.net.ip.IpClientCallbacks* com.android.server.x.wifi.net.ip.IpClientCallbacks@1 rule android.net.ip.IpClientManager* com.android.server.x.wifi.net.ip.IpClientManager@1 rule android.net.ip.IpClientUtil* com.android.server.x.wifi.net.ip.IpClientUtil@1 +rule android.net.ipmemorystore.OnBlobRetrievedListener* com.android.server.x.wifi.net.ipmemorystore.OnBlobRetrievedListener@1 +rule android.net.ipmemorystore.OnStatusListener* com.android.server.x.wifi.net.ipmemorystore.OnStatusListener@1 +rule android.net.ipmemorystore.StatusParcelable* @0 +rule android.net.ipmemorystore.Status* com.android.server.x.wifi.net.ipmemorystore.Status@1 +rule android.net.networkstack.ModuleNetworkStackClient* com.android.server.x.wifi.net.networkstack.ModuleNetworkStackClient@1 +rule android.net.networkstack.NetworkStackClientBase* com.android.server.x.wifi.net.networkstack.NetworkStackClientBase@1 rule android.net.shared.InetAddressUtils* com.android.server.x.wifi.net.shared.InetAddressUtils@1 rule android.net.shared.InitialConfiguration* com.android.server.x.wifi.net.shared.InitialConfiguration@1 rule android.net.shared.IpConfigurationParcelableUtil* com.android.server.x.wifi.net.shared.IpConfigurationParcelableUtil@1 +rule android.net.shared.Layer2Information* com.android.server.x.wifi.net.shared.Layer2Information@1 rule android.net.shared.LinkPropertiesParcelableUtil* com.android.server.x.wifi.net.shared.LinkPropertiesParcelableUtil@1 -rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 rule android.net.shared.NetdUtils* com.android.server.x.wifi.net.shared.NetdUtils@1 rule android.net.shared.NetworkMonitorUtils* com.android.server.x.wifi.net.shared.NetworkMonitorUtils@1 rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 @@ -28,6 +34,8 @@ rule android.net.util.SharedLog* com.android.server.x.wifi.net.util.SharedLog@1 rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1 rule android.net.util.IpUtils* com.android.server.x.wifi.net.util.IpUtils@1 +rule androidx.annotation.** com.android.server.x.wifi.androidx.annotation.@1 + # We don't jar-jar the entire package because, we still use some classes (like # AsyncChannel in com.android.internal.util) from these packages which are not # inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future). @@ -53,6 +61,13 @@ rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1 rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1 # Use our statically linked PlatformProperties library rule android.sysprop.** com.android.server.x.wifi.sysprop.@1 +# Use our statically linked HIDL stubs +rule android.hardware.** com.android.server.x.wifi.hardware.@1 +rule android.hidl.** com.android.server.x.wifi.hidl.@1 +# Use our statically linked ksoap2 +rule org.ksoap2.** com.android.server.x.wifi.ksoap2.@1 +# Use our statically linked nanohttpd +rule fi.iki.elonen.** com.android.server.x.wifi.elonen.@1 # used by both framework-wifi and wifi-service rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1 diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index f1be8b20eb53..6c8dc00cb579 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -179,6 +179,8 @@ public class WifiManager { /** * Reason code if one or more of the network suggestions added already exists in platform's * database. + * Note: this code will not be returned with Android 11 as in-place modification is allowed, + * please check {@link #addNetworkSuggestions(List)}. * @see WifiNetworkSuggestion#equals(Object) */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3; @@ -186,6 +188,8 @@ public class WifiManager { /** * Reason code if the number of network suggestions provided by the app crosses the max * threshold set per app. + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} if + * the total size exceeds the limit. * @see #getMaxNumberOfNetworkSuggestionsPerApp() */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4; @@ -193,21 +197,27 @@ public class WifiManager { /** * Reason code if one or more of the network suggestions removed does not exist in platform's * database. + * The framework won't remove any suggestions if one or more of suggestions provided + * by {@link #removeNetworkSuggestions(List)} does not exist in database. + * @see WifiNetworkSuggestion#equals(Object) */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; /** * Reason code if one or more of the network suggestions added is not allowed. - * + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} + * if one or more of them is not allowed. * This error may be caused by suggestion is using SIM-based encryption method, but calling app * is not carrier privileged. */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6; /** - * Reason code if one or more of the network suggestions added is invalid. - * - * Please user {@link WifiNetworkSuggestion.Builder} to create network suggestions. + * Reason code if one or more of the network suggestions added is invalid. Framework will reject + * all the suggestions in the list. + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} + * if one or more of them is invalid. + * Please use {@link WifiNetworkSuggestion.Builder} to create network suggestions. */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7; @@ -1887,7 +1897,7 @@ public class WifiManager { * <li> If user reset network settings, all added suggestions will be discarded. Apps can use * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li> * <li> In-place modification of existing suggestions are allowed. - * <li>If the provided suggestions includes any previously provided suggestions by the app, + * <li> If the provided suggestions include any previously provided suggestions by the app, * previous suggestions will be updated.</li> * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and * the device is currently connected to that suggested network, then the device will disconnect diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java index ed54ad13e5e1..737b7c7b9caf 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java @@ -21,7 +21,6 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.MacAddress; -import android.net.MatchAllNetworkSpecifier; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Parcel; @@ -553,13 +552,6 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc /** @hide */ @Override public boolean canBeSatisfiedBy(NetworkSpecifier other) { - if (this == other) { - return true; - } - // Any generic requests should be satisifed by a specific wifi network. - if (other == null || other instanceof MatchAllNetworkSpecifier) { - return true; - } if (other instanceof WifiNetworkAgentSpecifier) { return ((WifiNetworkAgentSpecifier) other).satisfiesNetworkSpecifier(this); } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java index 53a7d032da42..fc0ef469ad80 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java @@ -382,11 +382,11 @@ public class WifiNetworkSpecifierTest { /** * Validate NetworkSpecifier matching. * a) Create a network specifier for WPA_PSK network - * b) Ensure that the specifier matches {@code null} and {@link MatchAllNetworkSpecifier} + * b) Ensure that the specifier does not match {@code null} and {@link MatchAllNetworkSpecifier} * specifiers. */ @Test - public void testWifiNetworkSpecifierSatisfiesNullAndAllMatch() { + public void testWifiNetworkSpecifierDoesNotSatisfyNullAndAllMatch() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY; @@ -396,8 +396,8 @@ public class WifiNetworkSpecifierTest { MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration); - assertTrue(specifier.canBeSatisfiedBy(null)); - assertTrue(specifier.canBeSatisfiedBy(new MatchAllNetworkSpecifier())); + assertFalse(specifier.canBeSatisfiedBy(null)); + assertFalse(specifier.canBeSatisfiedBy(new MatchAllNetworkSpecifier())); } /** |